diff --git a/.gitignore b/.gitignore index aadaa13912c19dbc056245784442d8b4c9de2a82..6398c09fe796278130978c2a8598ee9270399388 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ rls*.log *.rej **/wip/*.stderr .local +**/hfuzz_target/ +**/hfuzz_workspace/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 98cf4dd1d8c4f6109f2402ee44c890405dbdf968..06afc350588196772cb5dd0d93e66288b14820ac 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,6 +24,7 @@ stages: - test - build + - post-build-test - publish - kubernetes - flaming-fir @@ -38,8 +39,8 @@ variables: DOCKER_OS: "debian:stretch" ARCH: "x86_64" # FIXME set to release - CARGO_UNLEASH_INSTALL_PARAMS: "--version 1.0.0-alpha.8" - CARGO_UNLEASH_PKG_DEF: "--skip node node-* pallet-template pallet-example pallet-example-* subkey chain-spec-builder" + CARGO_UNLEASH_INSTALL_PARAMS: "--version 1.0.0-alpha.10" + CARGO_UNLEASH_PKG_DEF: "--skip node node-* pallet-template pallet-example pallet-example-* subkey chain-spec-builder sp-arithmetic-fuzzer" .collect-artifacts: &collect-artifacts @@ -127,6 +128,17 @@ check-line-width: allow_failure: true + + +check-polkadot-companion-build: + stage: build + <<: *docker-env + script: + - ./.maintain/gitlab/check_polkadot_companion_build.sh + interruptible: true + allow_failure: true + + cargo-audit: stage: test <<: *docker-env @@ -136,6 +148,20 @@ cargo-audit: - cargo audit allow_failure: true +cargo-deny: + stage: test + <<: *docker-env + 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 cargo-check-benches: stage: test @@ -167,7 +193,7 @@ test-linux-stable: &test-linux variables: - $DEPLOY_TAG script: - - WASM_BUILD_NO_COLOR=1 time cargo test --all --release --verbose --locked | + - WASM_BUILD_NO_COLOR=1 time cargo test --all --release --verbose --locked |& tee output.log - sccache -s after_script: @@ -214,6 +240,24 @@ test-frame-staking: - WASM_BUILD_NO_COLOR=1 time cargo test --release --verbose --no-default-features --features std - sccache -s +test-frame-examples-compile-to-wasm: + stage: test + <<: *docker-env + 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 + except: + variables: + - $DEPLOY_TAG + 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-wasmtime: stage: test <<: *docker-env @@ -230,6 +274,22 @@ test-wasmtime: - WASM_BUILD_NO_COLOR=1 time cargo test --release --verbose --features wasmtime - sccache -s +test-runtime-benchmarks: + stage: test + <<: *docker-env + 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 + except: + variables: + - $DEPLOY_TAG + script: + - cd bin/node/cli + - BUILD_DUMMY_WASM_BINARY=1 time cargo check --verbose --features runtime-benchmarks + - sccache -s + test-linux-stable-int: <<: *test-linux except: @@ -271,7 +331,7 @@ check-web-wasm: - time cargo build --target=wasm32-unknown-unknown -p sp-consensus - time cargo build --target=wasm32-unknown-unknown -p sc-telemetry # Note: the command below is a bit weird because several Cargo issues prevent us from compiling the node in a more straight-forward way. - - time cargo build --manifest-path=bin/node/cli/Cargo.toml --no-default-features --features "browser" --target=wasm32-unknown-unknown + - time cargo +nightly build --manifest-path=bin/node/cli/Cargo.toml --no-default-features --features "browser" --target=wasm32-unknown-unknown -Z features=itarget - sccache -s test-full-crypto-feature: @@ -376,30 +436,17 @@ check_warnings: echo "___No warnings___"; fi -# Nightly check whether Polkadot 'master' branch builds. -check_polkadot: - stage: build - <<: *docker-env - allow_failure: true + +check-polkadot-companion-status: + stage: post-build-test + image: parity/tools:latest + <<: *kubernetes-build only: - - master - - schedules + - /^[0-9]+$/ script: - - SUBSTRATE_PATH=$(pwd) - # Clone the current Polkadot master branch into ./polkadot. - - git clone --depth 1 https://gitlab.parity.io/parity/polkadot.git - - cd polkadot - # Make sure we override the crates in native and wasm build - - mkdir .cargo - - echo "paths = [ \"$SUBSTRATE_PATH\" ]" > .cargo/config - - mkdir -p target/debug/wbuild/.cargo - - echo "paths = [ \"$SUBSTRATE_PATH\" ]" > target/debug/wbuild/.cargo/config - # package, others are updated along the way. - - cargo update - # Check whether Polkadot 'master' branch builds with this Substrate commit. - - time cargo check - - cd - - - sccache -s + - ./.maintain/gitlab/check_polkadot_companion_status.sh + + trigger-contracts-ci: stage: publish @@ -578,7 +625,7 @@ publish-to-crates-io: <<: *docker-env only: - tags - - /^v[0-9]+\.[0-9]+\.[0-9]+.*$/ + - /^ci-release-.*$/ script: - cargo install cargo-unleash ${CARGO_UNLEASH_INSTALL_PARAMS} - cargo unleash em-dragons --no-check ${CARGO_UNLEASH_PKG_DEF} diff --git a/.maintain/Dockerfile b/.maintain/Dockerfile index 2fc1532aa2837b3314eaf17a907ca765d33c85b5..21a41720f7d65ffd41a35cb1fa425e5999c9e289 100644 --- a/.maintain/Dockerfile +++ b/.maintain/Dockerfile @@ -39,6 +39,10 @@ RUN mv /usr/share/ca* /tmp && \ 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 && \ diff --git a/.maintain/deny.toml b/.maintain/deny.toml new file mode 100644 index 0000000000000000000000000000000000000000..8cc7635d5049be795e994b0ba740d9273b2f520f --- /dev/null +++ b/.maintain/deny.toml @@ -0,0 +1,193 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url of the advisory database to use +db-url = "https://github.com/rustsec/advisory-db" +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", +] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# The lint level for crates which do not have a detectable license +unlicensed = "deny" +# List of explictly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +allow = [ + #"MIT", + #"Apache-2.0", + #"Apache-2.0 WITH LLVM-exception", +] +# List of explictly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "allow" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "either" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.9 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +[[licenses.clarify]] +# The name of the crate the clarification applies to +name = "ring" +# THe optional version constraint for the crate +#version = "*" +# The SPDX expression for the license requirements of the crate +expression = "OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + { path = "LICENSE", hash = 0xbd0eed23 } +] +[[licenses.clarify]] +name = "webpki" +expression = "ISC" +license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "lowest-version" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# 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. +] +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "deny" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] diff --git a/.maintain/gitlab/check_polkadot_companion_build.sh b/.maintain/gitlab/check_polkadot_companion_build.sh new file mode 100755 index 0000000000000000000000000000000000000000..5bd51e0cd261fb162785719165b678f09afb266f --- /dev/null +++ b/.maintain/gitlab/check_polkadot_companion_build.sh @@ -0,0 +1,96 @@ +#!/bin/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 +# + + +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="Accept: application/vnd.github.v3+json" + +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. in absence of that string it will check if a polkadot +pr is mentioned and will use the last one instead. if none of the above can be +found it will check the build against polkadot:master. + + +EOT + + + +SUBSTRATE_PATH=$(pwd) + +# Clone the current Polkadot master branch into ./polkadot. +git clone --depth 1 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}" + # get the last reference to a pr in polkadot + pr_body="$(curl -H "${github_header}" -s ${github_api_substrate_pull_url}/${CI_COMMIT_REF_NAME} \ + | sed -n -r 's/^[[:space:]]+"body": (".*")[^"]+$/\1/p')" + + pr_companion="$(echo "${pr_body}" | sed -n -r \ + -e 's;^.*polkadot companion: paritytech/polkadot#([0-9]+).*$;\1;p' \ + -e 's;^.*polkadot companion: https://github.com/paritytech/polkadot/pull/([0-9]+).*$;\1;p' \ + | tail -n 1)" + if [ -z "${pr_companion}" ] + then + pr_companion="$(echo "${pr_body}" | sed -n -r \ + 's;^.*https://github.com/paritytech/polkadot/pull/([0-9]+).*$;\1;p' \ + | tail -n 1)" + fi + + if [ "${pr_companion}" ] + then + boldprint "companion pr specified/detected: #${pr_companion}" + git fetch --depth 1 origin refs/pull/${pr_companion}/head:pr/${pr_companion} + git checkout pr/${pr_companion} + else + boldprint "no companion pr found - building polkadot:master" + fi +else + boldprint "this is not a pull request - building polkadot:master" +fi + +# Make sure we override the crates in native and wasm build +# patching the git path as described in the link below did not test correctly +# https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html +mkdir .cargo +echo "paths = [ \"$SUBSTRATE_PATH\" ]" > .cargo/config + +mkdir -p target/debug/wbuild/.cargo +cp .cargo/config target/debug/wbuild/.cargo/config + +# package, others are updated along the way. +cargo update + +# Test Polkadot pr or master branch with this Substrate commit. +time cargo test --all --release --verbose + diff --git a/.maintain/gitlab/check_polkadot_companion_status.sh b/.maintain/gitlab/check_polkadot_companion_status.sh new file mode 100755 index 0000000000000000000000000000000000000000..b54f457dc56830ef1ad2892809025ff13cf4f38f --- /dev/null +++ b/.maintain/gitlab/check_polkadot_companion_status.sh @@ -0,0 +1,99 @@ +#!/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="Accept: application/vnd.github.v3+json" + +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 + +or any other polkadot pr is mentioned in this pr's description 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;^.*polkadot companion: paritytech/polkadot#([0-9]+).*$;\1;p' \ + -e 's;^.*polkadot companion: https://github.com/paritytech/polkadot/pull/([0-9]+).*$;\1;p' \ + | tail -n 1)" + +# get companion mentioned in the description +if [ -z "${pr_companion}" ] +then + pr_companion="$(echo "${pr_body}" | sed -n -r \ + 's;^.*https://github.com/paritytech/polkadot/pull/([0-9]+).*$;\1;p' \ + | tail -n 1)" +fi + +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 +# mergable and approved + +curl -H "${github_header}" -sS -o companion_pr.json \ + ${github_api_polkadot_pull_url}/${pr_companion} + +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 + +curl -H "${github_header}" -sS -o companion_pr_reviews.json \ + ${github_api_polkadot_pull_url}/${pr_companion}/reviews + +if [ "$(jq -r -e '.[].state' < companion_pr_reviews.json | uniq)" != "APPROVED" ] +then + boldprint "polkadot pr #${pr_companion} not APPROVED" + exit 1 +fi + +boldprint "polkadot pr #${pr_companion} state APPROVED" +exit 0 + + diff --git a/.maintain/gitlab/check_runtime.sh b/.maintain/gitlab/check_runtime.sh index 1eec8311efff4b1647eb52846aea0eac7f8fa1c5..5b7e25e3afc4eabef01d984047a98d675ab09757 100755 --- a/.maintain/gitlab/check_runtime.sh +++ b/.maintain/gitlab/check_runtime.sh @@ -33,7 +33,7 @@ git log --graph --oneline --decorate=short -n 10 boldprint "make sure the master branch and release tag are available in shallow clones" git fetch --depth=${GIT_DEPTH:-100} origin master git fetch --depth=${GIT_DEPTH:-100} origin release -git tag release FETCH_HEAD +git tag -f release FETCH_HEAD git log -n1 release diff --git a/.maintain/gitlab/generate_changelog.sh b/.maintain/gitlab/generate_changelog.sh new file mode 100755 index 0000000000000000000000000000000000000000..ba2a507e4cac694f918aa2c88cf1585991faa373 --- /dev/null +++ b/.maintain/gitlab/generate_changelog.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +# shellcheck source=lib.sh +source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/lib.sh" + +version="$2" +last_version="$1" + +all_changes="$(sanitised_git_logs "$last_version" "$version")" +runtime_changes="" +api_changes="" +client_changes="" +changes="" + +while IFS= read -r line; do + pr_id=$(echo "$line" | sed -E 's/.*#([0-9]+)\)$/\1/') + + # Skip if the PR has the silent label - this allows us to skip a few requests + if has_label 'paritytech/substrate' "$pr_id" 'B0-silent'; then + continue + fi + if has_label 'paritytech/substrate' "$pr_id" 'B1-runtimenoteworthy'; then + runtime_changes="$runtime_changes +$line" + fi + if has_label 'paritytech/substrate' "$pr_id" 'B1-clientnoteworthy'; then + client_changes="$client_changes +$line" + fi + if has_label 'paritytech/substrate' "$pr_id" 'B1-apinoteworthy' ; then + api_changes="$api_changes +$line" + continue + fi +done <<< "$all_changes" + +# Make the substrate section if there are any substrate changes +if [ -n "$runtime_changes" ] || + [ -n "$api_changes" ] || + [ -n "$client_changes" ]; then + changes=$(cat << EOF +Substrate changes +----------------- + +EOF +) + if [ -n "$runtime_changes" ]; then + changes="$changes + +Runtime +------- +$runtime_changes" + fi + if [ -n "$client_changes" ]; then + changes="$changes + +Client +------ +$client_changes" + fi + if [ -n "$api_changes" ]; then + changes="$changes + +API +--- +$api_changes" + fi + release_text="$release_text + +$changes" +fi + +echo "$changes" diff --git a/.maintain/gitlab/lib.sh b/.maintain/gitlab/lib.sh index c8b2d73e6097f42fd6590b5e3d78c537dc620028..ecc9a5f54288cd4636a05cb7f2a2d0535e40ebe9 100755 --- a/.maintain/gitlab/lib.sh +++ b/.maintain/gitlab/lib.sh @@ -15,11 +15,23 @@ sanitised_git_logs(){ } # Returns the last published release on github +# Note: we can't just use /latest because that ignores prereleases # repo: 'organization/repo' # Usage: last_github_release "$repo" last_github_release(){ - curl -H "Authorization: token $GITHUB_RELEASE_TOKEN" \ - -s "$api_base/$1/releases/latest" | jq '.tag_name' + i=0 + # Iterate over releases until we find the last release that's not just a draft + while [ $i -lt 29 ]; do + out=$(curl -H "Authorization: token $GITHUB_RELEASE_TOKEN" -s "$api_base/$1/releases" | jq ".[$i]") + echo "$out" + # Ugh when echoing to jq, we need to translate newlines into spaces :/ + if [ "$(echo "$out" | tr '\r\n' ' ' | jq '.draft')" = "false" ]; then + echo "$out" | tr '\r\n' ' ' | jq '.tag_name' + return + else + i=$((i + 1)) + fi + done } # Checks whether a tag on github has been verified diff --git a/.maintain/gitlab/publish_draft_release.sh b/.maintain/gitlab/publish_draft_release.sh index 4f73575f5bbaf836d7e07cb43c7fd1c1173858da..c5813718a69f27636b2729d8bc2cc7fbf05561fa 100755 --- a/.maintain/gitlab/publish_draft_release.sh +++ b/.maintain/gitlab/publish_draft_release.sh @@ -3,44 +3,12 @@ # shellcheck source=lib.sh source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/lib.sh" -# Substrate labels for PRs we want to include in the release notes -labels=( - 'B1-runtimenoteworthy' - 'B1-clientnoteworthy' - 'B1-apinoteworthy' -) - version="$CI_COMMIT_TAG" # Note that this is not the last *tagged* version, but the last *published* version last_version=$(last_github_release 'paritytech/substrate') -echo "[+] Version: $version; Previous version: $last_version" - -all_changes="$(sanitised_git_logs "$last_version" "$version")" -labelled_changes="" -echo "[+] Iterating through $(wc -l <<< "$all_changes") changes to find labelled PRs" -while IFS= read -r line; do - pr_id=$(echo "$line" | sed -E 's/.*#([0-9]+)\)$/\1/') - - # Skip if the PR has the silent label - this allows us to skip a few requests - if has_label 'paritytech/substrate' "$pr_id" 'B0-silent'; then - continue - fi - for label in "${labels[@]}"; do - if has_label 'paritytech/substrate' "$pr_id" "$label"; then - labelled_changes="$labelled_changes -$line" - fi - done -done <<< "$all_changes" - - -release_text="Substrate $version ------------------ -$labelled_changes" -echo "[+] Release text generated: " -echo "$release_text" +release_text="$(./generate_release_text.sh "$last_version" "$version")" echo "[+] Pushing release to github" # Create release on github diff --git a/.maintain/node-template-release/Cargo.toml b/.maintain/node-template-release/Cargo.toml index 606def19bb99f1002058d1d868181cbc9c0fbb56..dd3166d58ddf463580474a02ea70efdcdbf00228 100644 --- a/.maintain/node-template-release/Cargo.toml +++ b/.maintain/node-template-release/Cargo.toml @@ -16,3 +16,6 @@ git2 = "0.8" flate2 = "1.0" [workspace] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/Cargo.lock b/Cargo.lock index cb7d7c713b9ed65c6c2bd0d572348a873b7de518..4e16401288b31eef23f44ff2ff45a2543b9ef812 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,13 +61,24 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5e63fd144e18ba274ae7095c0197a870a7b9468abc801dd62f190d80817d2ec" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" dependencies = [ "memchr", ] +[[package]] +name = "alga" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f823d037a7ec6ea2197046bafd4ae150e6bc36f9ca347404f46a46823fa84f2" +dependencies = [ + "approx", + "num-complex", + "num-traits", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -88,9 +99,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" +checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff" [[package]] name = "app_dirs" @@ -104,11 +115,26 @@ dependencies = [ "xdg", ] +[[package]] +name = "approx" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75153c95fdedd7db9732dfbfc3702324a1627eec91ba56e37cd0ac78314ab2ed" + [[package]] name = "arc-swap" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" +checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" [[package]] name = "arrayref" @@ -152,15 +178,15 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "0.12.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6283bac8dd7226470d491bc4737816fea4ca1fba7a2847f2e9097fd6bfb4624c" +checksum = "c88b9ca26f9c16ec830350d309397e74ee9abdfd8eb1f71cb6ecc71a3fc818da" dependencies = [ "doc-comment", - "escargot", "predicates", "predicates-core", "predicates-tree", + "wait-timeout", ] [[package]] @@ -184,7 +210,7 @@ dependencies = [ "futures-io", "futures-timer 2.0.2", "kv-log-macro", - "log 0.4.8", + "log", "memchr", "mio", "mio-uds", @@ -242,9 +268,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.44" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4036b9bf40f3cf16aba72a3d65e8a520fc4bafcdc7079aea8f848c58c5b5536" +checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" dependencies = [ "backtrace-sys", "cfg-if", @@ -254,9 +280,9 @@ dependencies = [ [[package]] name = "backtrace-sys" -version = "0.1.32" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" dependencies = [ "cc", "libc", @@ -268,16 +294,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" -[[package]] -name = "base64" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -dependencies = [ - "byteorder 1.3.4", - "safemem", -] - [[package]] name = "base64" version = "0.10.1" @@ -305,9 +321,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.53.1" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99de13bb6361e01e493b3db7928085dcc474b7ba4f5481818e53a89d76b8393f" +checksum = "6bb26d6a69a335b8cb0e7c7e9775cd5666611dc50a37177c3f2cedcfc040e8c8" dependencies = [ "bitflags", "cexpr", @@ -317,7 +333,7 @@ dependencies = [ "env_logger 0.7.1", "lazy_static", "lazycell", - "log 0.4.8", + "log", "peeking_take_while", "proc-macro2", "quote", @@ -341,9 +357,13 @@ checksum = "5da9b3d9f6f585199287a473f4f8dfab6566cf827d15c00c219f53c645687ead" [[package]] name = "bitvec" -version = "0.15.2" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6" +checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" +dependencies = [ + "either", + "radium", +] [[package]] name = "blake2" @@ -430,9 +450,9 @@ checksum = "b170cd256a3f9fa6b9edae3e44a7dfdfc77e8124dbc3e2612d75f9c3e2396dae" [[package]] name = "bstr" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502ae1441a0a5adb8fbd38a5955a6416b9493e92b465de5e4a9bde6a539c2c48" +checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" dependencies = [ "lazy_static", "memchr", @@ -451,9 +471,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" +checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" [[package]] name = "byte-slice-cast" @@ -496,15 +516,6 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -dependencies = [ - "ppv-lite86", -] - [[package]] name = "c_linked_list" version = "1.1.1" @@ -543,9 +554,9 @@ dependencies = [ [[package]] name = "cexpr" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" dependencies = [ "nom", ] @@ -567,7 +578,7 @@ dependencies = [ [[package]] name = "chain-spec-builder" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "ansi_term 0.12.1", "node-cli", @@ -579,9 +590,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" dependencies = [ "js-sys", "num-integer", @@ -592,9 +603,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "0.28.1" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" +checksum = "f92986241798376849e1a007827041fed9bb36195822c2049d18e174420e0534" dependencies = [ "glob 0.3.0", "libc", @@ -659,7 +670,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7871d2947441b0fdd8e2bd1ce2a2f75304f896582c0d572162d48290683c48" dependencies = [ - "log 0.4.8", + "log", "web-sys", ] @@ -689,32 +700,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "core-foundation" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" -dependencies = [ - "core-foundation-sys 0.6.2", - "libc", -] - [[package]] name = "core-foundation" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" dependencies = [ - "core-foundation-sys 0.7.0", + "core-foundation-sys", "libc", ] -[[package]] -name = "core-foundation-sys" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" - [[package]] name = "core-foundation-sys" version = "0.7.0" @@ -724,7 +719,8 @@ checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] name = "cranelift-bforest" version = "0.59.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a9c21f8042b9857bda93f6c1910b9f9f24100187a3d3d52f214a34e3dc5818" dependencies = [ "cranelift-entity", ] @@ -732,7 +728,8 @@ dependencies = [ [[package]] name = "cranelift-codegen" version = "0.59.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7853f77a6e4a33c67a69c40f5e1bb982bd2dc5c4a22e17e67b65bbccf9b33b2e" dependencies = [ "byteorder 1.3.4", "cranelift-bforest", @@ -740,7 +737,7 @@ dependencies = [ "cranelift-codegen-shared", "cranelift-entity", "gimli", - "log 0.4.8", + "log", "serde", "smallvec 1.2.0", "target-lexicon", @@ -750,7 +747,8 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" version = "0.59.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084cd6d5fb0d1da28acd72c199471bfb09acc703ec8f3bf07b1699584272a3b9" dependencies = [ "cranelift-codegen-shared", "cranelift-entity", @@ -759,12 +757,14 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" version = "0.59.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "701b599783305a58c25027a4d73f2d6b599b2d8ef3f26677275f480b4d51e05d" [[package]] name = "cranelift-entity" version = "0.59.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88e792b28e1ebbc0187b72ba5ba880dad083abe9231a99d19604d10c9e73f38" dependencies = [ "serde", ] @@ -772,10 +772,11 @@ dependencies = [ [[package]] name = "cranelift-frontend" version = "0.59.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518344698fa6c976d853319218415fdfb4f1bc6b42d0b2e2df652e55dff1f778" dependencies = [ "cranelift-codegen", - "log 0.4.8", + "log", "smallvec 1.2.0", "target-lexicon", ] @@ -783,7 +784,8 @@ dependencies = [ [[package]] name = "cranelift-native" version = "0.59.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32daf082da21c0c05d93394ff4842c2ab7c4991b1f3186a1d952f8ac660edd0b" dependencies = [ "cranelift-codegen", "raw-cpuid", @@ -793,12 +795,13 @@ dependencies = [ [[package]] name = "cranelift-wasm" version = "0.59.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2aa816f554a3ef739a5d17ca3081a1f8983f04c944ea8ff60fb8d9dd8cd2d7b" dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "log 0.4.8", + "log", "serde", "thiserror", "wasmparser", @@ -1020,19 +1023,6 @@ dependencies = [ "rand 0.3.23", ] -[[package]] -name = "curve25519-dalek" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d" -dependencies = [ - "byteorder 1.3.4", - "clear_on_drop", - "digest", - "rand_core 0.3.1", - "subtle 2.2.2", -] - [[package]] name = "curve25519-dalek" version = "2.0.0" @@ -1043,7 +1033,7 @@ dependencies = [ "digest", "rand_core 0.5.1", "subtle 2.2.2", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -1054,9 +1044,9 @@ checksum = "11c0346158a19b3627234e15596f5e465c360fcdb97d817bcb255e0510f5a788" [[package]] name = "derive_more" -version = "0.99.3" +version = "0.99.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a806e96c59a76a5ba6e18735b6cf833344671e61e7863f2edb5c518ea2cac95c" +checksum = "e2323f3f47db9a0e77ce7a300605d8d2098597fc451ed1a97bb1f6411bb550a7" dependencies = [ "proc-macro2", "quote", @@ -1112,9 +1102,9 @@ dependencies = [ [[package]] name = "doc-comment" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "ed25519-dalek" @@ -1123,7 +1113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" dependencies = [ "clear_on_drop", - "curve25519-dalek 2.0.0", + "curve25519-dalek", "rand 0.7.3", "sha2", ] @@ -1162,7 +1152,7 @@ checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" dependencies = [ "atty", "humantime", - "log 0.4.8", + "log", "regex", "termcolor", ] @@ -1175,7 +1165,7 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime", - "log 0.4.8", + "log", "regex", "termcolor", ] @@ -1188,18 +1178,18 @@ checksum = "516aa8d7a71cb00a1c4146f0798549b93d083d4f189b3ced8f3de6b8f11ee6c4" [[package]] name = "erased-serde" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7d80305c9bd8cd78e3c753eb9fb110f83621e5211f1a3afffcc812b104daf9" +checksum = "d88b6d1705e16a4d62e05ea61cc0496c2bd190f4fa8e5c1f11ce747be6bcf3d1" dependencies = [ "serde", ] [[package]] name = "errno" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e" +checksum = "b480f641ccf0faf324e20c1d3e53d81b7484c698b42ea677f6907ae4db195371" dependencies = [ "errno-dragonfly", "libc", @@ -1216,50 +1206,38 @@ dependencies = [ "libc", ] -[[package]] -name = "escargot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74cf96bec282dcdb07099f7e31d9fed323bca9435a09aba7b6d99b7617bca96d" -dependencies = [ - "lazy_static", - "log 0.4.8", - "serde", - "serde_json", -] - [[package]] name = "ethbloom" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cfe1c169414b709cf28aa30c74060bdb830a03a8ba473314d079ac79d80a5f" +checksum = "9e7abcddbdd5db30aeed4deb586adc4824e6c247e2f7238d1187f752893f096b" dependencies = [ "crunchy", "fixed-hash", "impl-rlp", - "impl-serde 0.2.3", - "tiny-keccak 1.5.0", + "impl-serde 0.3.0", + "tiny-keccak 2.0.1", ] [[package]] name = "ethereum-types" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba744248e3553a393143d5ebb68939fc3a4ec0c22a269682535f5ffe7fed728c" +checksum = "964c23cdee0ca07d5be2a628b46d5c11a2134ce554a8c16d8dbc2db647e4fd4d" dependencies = [ "ethbloom", "fixed-hash", "impl-rlp", - "impl-serde 0.2.3", + "impl-serde 0.3.0", "primitive-types", "uint", ] [[package]] name = "evm" -version = "0.15.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "272f65e18a2b6449b682bfcdf6c3ccc63db0b93898d89c0fb237548bbfc764a5" +checksum = "23a5c0ebf219b2b878bde1838282e0bb69828338df37fd136f1e93182ae35a59" dependencies = [ "evm-core", "evm-gasometer", @@ -1272,18 +1250,18 @@ dependencies = [ [[package]] name = "evm-core" -version = "0.15.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66534d42e13d50f9101bed87cb568fd5aa929c600c3c13f8dadbbf39f5635945" +checksum = "d944a07232006a3435df8aa014fd364ed04cb28d731782339e9c56436594f2d4" dependencies = [ "primitive-types", ] [[package]] name = "evm-gasometer" -version = "0.15.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39bc5b592803ca644781fe2290b7305ea5182f7c9516d615ddfb2308c2cab639" +checksum = "6a0d986953234d3786d0ca1beaaabab6a581d2128f8ec36c8c57e9c45e3d2b32" dependencies = [ "evm-core", "evm-runtime", @@ -1292,9 +1270,9 @@ dependencies = [ [[package]] name = "evm-runtime" -version = "0.15.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389e4b447fb26971a9c76c8aa49c0ab435f8e46e8fc46e1bc4ebf01f3c2b428f" +checksum = "1833c22f9518007d3cc28e14ff586263543516a1c7a147b260c603e4deb95403" dependencies = [ "evm-core", "primitive-types", @@ -1319,7 +1297,7 @@ dependencies = [ "anyhow", "goblin", "indexmap", - "log 0.4.8", + "log", "scroll", "string-interner", "target-lexicon", @@ -1362,9 +1340,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fdlimit" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9084c55bb76efb1496328976db88320fe7d9ee86e649e83c4ecce3ba7a9a35d1" +checksum = "0da54a593b34c71b889ee45f5b5bb900c74148c5f7f8c6a9479ee7899f69603c" dependencies = [ "libc", ] @@ -1376,18 +1354,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505b75b31ef7285168dd237c4a7db3c1f3e0927e7d314e670bc98e854272fe9" dependencies = [ "env_logger 0.6.2", - "log 0.4.8", + "log", ] [[package]] name = "finality-grandpa" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbb25bef9fcad97fb31e817da280b1c9174435b8769c770ee190a330dd181ea" +checksum = "024517816630be5204eba201e8d1d405042b1255a5e0e3f298b054fc24d59e1d" dependencies = [ "futures 0.3.4", "futures-timer 2.0.2", - "log 0.4.8", + "log", "num-traits", "parity-scale-codec", "parking_lot 0.9.0", @@ -1396,12 +1374,11 @@ dependencies = [ [[package]] name = "fixed-hash" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3367952ceb191f4ab95dd5685dc163ac539e36202f9fcfd0cb22f9f9c542fefc" +checksum = "32529fc42e86ec06e5047092082aab9ad459b070c5d2a76b14f4f5ce70bf2e84" dependencies = [ "byteorder 1.3.4", - "libc", "rand 0.7.3", "rustc-hex", "static_assertions", @@ -1415,9 +1392,9 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "flate2" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" +checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" dependencies = [ "cfg-if", "crc32fast", @@ -1432,34 +1409,20 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "fork-tree" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", ] [[package]] name = "frame-benchmarking" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", + "linregress", "parity-scale-codec", "sp-api", "sp-io", @@ -1470,7 +1433,7 @@ dependencies = [ [[package]] name = "frame-benchmarking-cli" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-benchmarking", "parity-scale-codec", @@ -1480,6 +1443,7 @@ dependencies = [ "sc-executor", "sc-service", "sp-core", + "sp-externalities", "sp-runtime", "sp-state-machine", "structopt", @@ -1487,7 +1451,7 @@ dependencies = [ [[package]] name = "frame-executive" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -1506,7 +1470,7 @@ dependencies = [ [[package]] name = "frame-metadata" -version = "11.0.0-alpha.3" +version = "11.0.0-alpha.5" dependencies = [ "parity-scale-codec", "serde", @@ -1516,14 +1480,14 @@ dependencies = [ [[package]] name = "frame-support" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "bitmask", "frame-metadata", "frame-support-procedural", "frame-system", "impl-trait-for-tuples", - "log 0.4.8", + "log", "once_cell", "parity-scale-codec", "paste", @@ -1541,7 +1505,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support-procedural-tools", "proc-macro2", @@ -1551,7 +1515,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate", @@ -1562,7 +1526,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "proc-macro2", "quote", @@ -1587,7 +1551,7 @@ dependencies = [ [[package]] name = "frame-system" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "criterion 0.2.11", "frame-support", @@ -1605,7 +1569,7 @@ dependencies = [ [[package]] name = "frame-system-rpc-runtime-api" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", "sp-api", @@ -1732,7 +1696,7 @@ dependencies = [ "futures 0.1.29", "futures 0.3.4", "lazy_static", - "log 0.4.8", + "log", "parking_lot 0.9.0", "pin-project", "serde", @@ -1919,22 +1883,22 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" +checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120" dependencies = [ "aho-corasick", "bstr", "fnv", - "log 0.4.8", + "log", "regex", ] [[package]] name = "gloo-timers" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2d17dbd803c2fc86cb1b613adf63192046a7176f383a8302594654752c4c4a" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" dependencies = [ "futures-channel", "futures-core", @@ -1949,7 +1913,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3081214398d39e4bd7f2c1975f0488ed04614ffdd976c6fc7a0708278552c0da" dependencies = [ - "log 0.4.8", + "log", "plain", "scroll", ] @@ -1966,7 +1930,7 @@ dependencies = [ "futures 0.1.29", "http 0.1.21", "indexmap", - "log 0.4.8", + "log", "slab", "string", "tokio-io", @@ -1974,20 +1938,20 @@ dependencies = [ [[package]] name = "h2" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" +checksum = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42" dependencies = [ "bytes 0.5.4", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.0", + "http 0.2.1", "indexmap", - "log 0.4.8", + "log", "slab", - "tokio 0.2.12", + "tokio 0.2.13", "tokio-util", ] @@ -2027,9 +1991,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" +checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" dependencies = [ "libc", ] @@ -2080,6 +2044,17 @@ dependencies = [ "hmac", ] +[[package]] +name = "honggfuzz" +version = "0.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3de2c3273ef7735df1c5a72128ca85b1d20105b9aac643cdfd7a6e581311150" +dependencies = [ + "arbitrary", + "lazy_static", + "memmap", +] + [[package]] name = "http" version = "0.1.21" @@ -2093,9 +2068,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" dependencies = [ "bytes 0.5.4", "fnv", @@ -2121,7 +2096,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" dependencies = [ "bytes 0.5.4", - "http 0.2.0", + "http 0.2.1", ] [[package]] @@ -2139,25 +2114,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "hyper" -version = "0.10.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" -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.12.35" @@ -2173,7 +2129,7 @@ dependencies = [ "httparse", "iovec", "itoa", - "log 0.4.8", + "log", "net2", "rustc_version", "time", @@ -2190,24 +2146,24 @@ dependencies = [ [[package]] name = "hyper" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1c527bbc634be72aa7ba31e4e4def9bbb020f5416916279b7c705cd838893e" +checksum = "ed6081100e960d9d74734659ffc9cc91daf1c0fc7aceb8eaa94ee1a3f5046f2e" dependencies = [ "bytes 0.5.4", "futures-channel", "futures-core", "futures-util", - "h2 0.2.1", - "http 0.2.0", + "h2 0.2.4", + "http 0.2.1", "http-body 0.3.1", "httparse", "itoa", - "log 0.4.8", + "log", "net2", "pin-project", "time", - "tokio 0.2.12", + "tokio 0.2.13", "tower-service", "want 0.3.0", ] @@ -2221,28 +2177,15 @@ dependencies = [ "bytes 0.5.4", "ct-logs", "futures-util", - "hyper 0.13.2", - "log 0.4.8", + "hyper 0.13.4", + "log", "rustls 0.17.0", "rustls-native-certs", - "tokio 0.2.12", + "tokio 0.2.13", "tokio-rustls", "webpki", ] -[[package]] -name = "hyper-tls" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "hyper 0.12.35", - "native-tls", - "tokio-io", -] - [[package]] name = "idna" version = "0.1.5" @@ -2333,6 +2276,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "141340095b15ed7491bd3d4ced9d20cebfb826174b6bb03386381f62b01e3d77" +[[package]] +name = "intervalier" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14200459dc2319eb13708aed1c1efb8307e0e0e801e7282476939492e1492631" +dependencies = [ + "futures 0.3.4", + "futures-timer 2.0.2", +] + [[package]] name = "iovec" version = "0.1.4" @@ -2344,9 +2297,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a859057dc563d1388c1e816f98a1892629075fc046ed06e845b883bb8b2916fb" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" [[package]] name = "itertools" @@ -2374,9 +2327,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.35" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7889c7c36282151f6bf465be4700359318aef36baa951462382eae49e9577cf9" +checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" dependencies = [ "wasm-bindgen", ] @@ -2390,15 +2343,12 @@ dependencies = [ "failure", "futures 0.1.29", "hyper 0.12.35", - "hyper-tls", "jsonrpc-core", "jsonrpc-pubsub", - "log 0.4.8", + "log", "serde", "serde_json", - "tokio 0.1.22", "url 1.7.2", - "websocket", ] [[package]] @@ -2408,7 +2358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe3b688648f1ef5d5072229e2d672ecb92cbff7d1c79bcf3fd5898f3f3df0970" dependencies = [ "futures 0.1.29", - "log 0.4.8", + "log", "serde", "serde_derive", "serde_json", @@ -2444,10 +2394,10 @@ dependencies = [ "hyper 0.12.35", "jsonrpc-core", "jsonrpc-server-utils", - "log 0.4.8", + "log", "net2", "parking_lot 0.10.0", - "unicase 2.6.0", + "unicase", ] [[package]] @@ -2457,7 +2407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b31c9b90731276fdd24d896f31bb10aecf2e5151733364ae81123186643d939" dependencies = [ "jsonrpc-core", - "log 0.4.8", + "log", "parking_lot 0.10.0", "serde", ] @@ -2472,10 +2422,10 @@ dependencies = [ "globset", "jsonrpc-core", "lazy_static", - "log 0.4.8", + "log", "tokio 0.1.22", "tokio-codec", - "unicase 2.6.0", + "unicase", ] [[package]] @@ -2486,7 +2436,7 @@ checksum = "b94e5773b2ae66e0e02c80775ce6bbba6f15d5bb47c14ec36a36fcf94f8df851" dependencies = [ "jsonrpc-core", "jsonrpc-server-utils", - "log 0.4.8", + "log", "parking_lot 0.10.0", "slab", "ws", @@ -2525,25 +2475,24 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb" dependencies = [ - "log 0.4.8", + "log", ] [[package]] name = "kvdb" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03080afe6f42cd996da9f568d6add5d7fb5ee2ea7fb7802d2d2cbd836958fd87" +checksum = "cad096c6849b2ef027fabe35c4aed356d0e3d3f586d0a8361e5e17f1e50a7ce5" dependencies = [ - "parity-bytes", "parity-util-mem", "smallvec 1.2.0", ] [[package]] name = "kvdb-memorydb" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9355274e5a9e0a7e8ef43916950eae3949024de2a8dffe4d5a6c13974a37c8e" +checksum = "4aa954d12cfac958822dfd77aab34f3eec71f103b918c4ab79ab59a36ee594ea" dependencies = [ "kvdb", "parity-util-mem", @@ -2552,14 +2501,14 @@ dependencies = [ [[package]] name = "kvdb-rocksdb" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fecd50b14a534125228d7039951f92aaff742aff151c04546347aba4d3b4fbc" +checksum = "b3f14c3a10c8894d26175e57e9e26032e6d6c49c30cbe2468c5bf5f6b64bb0be" dependencies = [ "fs-swap", "interleaved-ordered", "kvdb", - "log 0.4.8", + "log", "num_cpus", "owning_ref", "parity-util-mem", @@ -2571,27 +2520,21 @@ dependencies = [ [[package]] name = "kvdb-web" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a985c47b4c46429e96033ebf6eaed784a81ceccb4e5df13d63f3b9078a4df81" +checksum = "26f96eec962af83cdf7c83036b3dbb0ae6a1249ddab746820618e2567ca8ebcd" dependencies = [ "futures 0.3.4", "js-sys", "kvdb", "kvdb-memorydb", - "log 0.4.8", + "log", "parity-util-mem", "send_wrapper 0.3.0", "wasm-bindgen", "web-sys", ] -[[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" @@ -2612,9 +2555,9 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" [[package]] name = "libc" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" +checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" [[package]] name = "libloading" @@ -2626,6 +2569,12 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "libm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" + [[package]] name = "libp2p" version = "0.16.2" @@ -2678,7 +2627,7 @@ dependencies = [ "futures-timer 3.0.2", "lazy_static", "libsecp256k1", - "log 0.4.8", + "log", "multistream-select", "parity-multiaddr", "parity-multihash", @@ -2694,7 +2643,7 @@ dependencies = [ "thiserror", "unsigned-varint", "void", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -2726,7 +2675,7 @@ checksum = "b99e552f9939b606eb4b59f7f64d9b01e3f96752f47e350fc3c5fc646ed3f649" dependencies = [ "futures 0.3.4", "libp2p-core", - "log 0.4.8", + "log", ] [[package]] @@ -2760,7 +2709,7 @@ dependencies = [ "futures_codec", "libp2p-core", "libp2p-swarm", - "log 0.4.8", + "log", "lru", "prost", "prost-build", @@ -2780,7 +2729,7 @@ dependencies = [ "futures 0.3.4", "libp2p-core", "libp2p-swarm", - "log 0.4.8", + "log", "prost", "prost-build", "smallvec 1.2.0", @@ -2801,7 +2750,7 @@ dependencies = [ "futures_codec", "libp2p-core", "libp2p-swarm", - "log 0.4.8", + "log", "parity-multihash", "prost", "prost-build", @@ -2828,7 +2777,7 @@ dependencies = [ "lazy_static", "libp2p-core", "libp2p-swarm", - "log 0.4.8", + "log", "net2", "rand 0.7.3", "smallvec 1.2.0", @@ -2847,7 +2796,7 @@ dependencies = [ "futures 0.3.4", "futures_codec", "libp2p-core", - "log 0.4.8", + "log", "parking_lot 0.10.0", "unsigned-varint", ] @@ -2858,11 +2807,11 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15a8a3d71f898beb6f854c8aae27aa1d198e0d1f2e49412261c2d90ef39675a" dependencies = [ - "curve25519-dalek 2.0.0", + "curve25519-dalek", "futures 0.3.4", "lazy_static", "libp2p-core", - "log 0.4.8", + "log", "prost", "prost-build", "rand 0.7.3", @@ -2870,7 +2819,7 @@ dependencies = [ "snow", "static_assertions", "x25519-dalek", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -2882,7 +2831,7 @@ dependencies = [ "futures 0.3.4", "libp2p-core", "libp2p-swarm", - "log 0.4.8", + "log", "rand 0.7.3", "void", "wasm-timer", @@ -2898,7 +2847,7 @@ dependencies = [ "futures 0.3.4", "futures_codec", "libp2p-core", - "log 0.4.8", + "log", "prost", "prost-build", "rw-stream-sink", @@ -2913,7 +2862,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b916938a8868f75180aeeffcc6a516a922d165e8fa2a90b57bad989d1ccbb57a" dependencies = [ "futures 0.3.4", - "log 0.4.8", + "log", "pin-project", "rand 0.7.3", "salsa20", @@ -2933,7 +2882,7 @@ dependencies = [ "js-sys", "lazy_static", "libp2p-core", - "log 0.4.8", + "log", "parity-send-wrapper", "pin-project", "prost", @@ -2958,7 +2907,7 @@ checksum = "275471e7c0e88ae004660866cd54f603bd8bd1f4caef541a27f50dd8640c4d4c" dependencies = [ "futures 0.3.4", "libp2p-core", - "log 0.4.8", + "log", "smallvec 1.2.0", "void", "wasm-timer", @@ -2976,7 +2925,7 @@ dependencies = [ "get_if_addrs", "ipnet", "libp2p-core", - "log 0.4.8", + "log", ] [[package]] @@ -2988,7 +2937,7 @@ dependencies = [ "async-std", "futures 0.3.4", "libp2p-core", - "log 0.4.8", + "log", ] [[package]] @@ -3016,7 +2965,7 @@ dependencies = [ "either", "futures 0.3.4", "libp2p-core", - "log 0.4.8", + "log", "quicksink", "rustls 0.16.0", "rw-stream-sink", @@ -3095,21 +3044,32 @@ dependencies = [ ] [[package]] -name = "lock_api" -version = "0.3.3" +name = "linregress" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" +checksum = "9290cf6f928576eeb9c096c6fad9d8d452a0a1a70a2bbffa6e36064eedc0aac9" dependencies = [ - "scopeguard", + "failure", + "nalgebra", + "statrs", ] [[package]] -name = "log" -version = "0.3.9" +name = "lite-json" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa835713bb12ba5204013497da16caf2dd2eee25ca829d0efaa054fb38c4ddd" +dependencies = [ + "paste", +] + +[[package]] +name = "lock_api" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" dependencies = [ - "log 0.4.8", + "scopeguard", ] [[package]] @@ -3145,6 +3105,15 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "matrixmultiply" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4f7ec66360130972f34830bfad9ef05c6610a43938a467bcc9ab9369ab3478f" +dependencies = [ + "rawpointer", +] + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3157,20 +3126,30 @@ 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 0.3.8", +] + [[package]] name = "memoffset" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" +checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" dependencies = [ - "rustc_version", + "autocfg 1.0.0", ] [[package]] name = "memory-db" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198831fe8722331a395bc199a5d08efbc197497ef354cb4c77b969c02ffc0fc4" +checksum = "f58381b20ebe2c578e75dececd9da411414903415349548ccc46aac3209cdfbc" dependencies = [ "ahash", "hash-db", @@ -3186,23 +3165,14 @@ checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" [[package]] name = "merlin" -version = "1.3.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0942b357c1b4d0dc43ba724674ec89c3218e6ca2b3e8269e7cb53bcecd2f6e" +checksum = "c6feca46f4fa3443a01769d768727f10c10a20fdb65e52dc16a81f0c8269bb78" dependencies = [ "byteorder 1.3.4", "keccak", - "rand_core 0.4.2", - "zeroize 1.1.0", -] - -[[package]] -name = "mime" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" -dependencies = [ - "log 0.3.9", + "rand_core 0.5.1", + "zeroize", ] [[package]] @@ -3226,7 +3196,7 @@ dependencies = [ "iovec", "kernel32-sys", "libc", - "log 0.4.8", + "log", "miow", "net2", "slab", @@ -3240,7 +3210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", - "log 0.4.8", + "log", "mio", "slab", ] @@ -3276,9 +3246,9 @@ checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" [[package]] name = "multimap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97fbd5d00e0e37bfb10f433af8f5aaf631e739368dc9fc28286ca81ca4948dc" +checksum = "d8883adfde9756c1d30b0f519c9b8c502a94b41ac62f696453c37c7fc0a958ce" [[package]] name = "multistream-select" @@ -3288,37 +3258,36 @@ checksum = "f938ffe420493e77c8b6cbcc3f282283f68fc889c5dcbc8e51668d5f3a01ad94" dependencies = [ "bytes 0.5.4", "futures 0.1.29", - "log 0.4.8", + "log", "smallvec 1.2.0", "tokio-io", "unsigned-varint", ] [[package]] -name = "names" -version = "0.11.0" +name = "nalgebra" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef320dab323286b50fb5cdda23f61c796a72a89998ab565ca32525c5c556f2da" +checksum = "aaa9fddbc34c8c35dd2108515587b8ce0cab396f17977b8c738568e4edb521a2" dependencies = [ - "rand 0.3.23", + "alga", + "approx", + "generic-array", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "rand 0.6.5", + "typenum", ] [[package]] -name = "native-tls" -version = "0.2.3" +name = "names" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" +checksum = "ef320dab323286b50fb5cdda23f61c796a72a89998ab565ca32525c5c556f2da" dependencies = [ - "lazy_static", - "libc", - "log 0.4.8", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework 0.3.4", - "security-framework-sys 0.3.3", - "tempfile", + "rand 0.3.23", ] [[package]] @@ -3347,7 +3316,7 @@ dependencies = [ [[package]] name = "node-cli" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "assert_cmd", "frame-benchmarking-cli", @@ -3356,7 +3325,7 @@ dependencies = [ "futures 0.3.4", "hex-literal", "jsonrpc-core", - "log 0.4.8", + "log", "nix", "node-executor", "node-inspect", @@ -3369,6 +3338,7 @@ dependencies = [ "pallet-contracts", "pallet-im-online", "pallet-indices", + "pallet-staking", "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec", @@ -3418,7 +3388,7 @@ dependencies = [ [[package]] name = "node-executor" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "criterion 0.3.1", "frame-benchmarking", @@ -3452,10 +3422,10 @@ dependencies = [ [[package]] name = "node-inspect" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "derive_more", - "log 0.4.8", + "log", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -3468,7 +3438,7 @@ dependencies = [ [[package]] name = "node-primitives" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "pretty_assertions", "sp-core", @@ -3478,7 +3448,7 @@ dependencies = [ [[package]] name = "node-rpc" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "jsonrpc-core", "node-primitives", @@ -3501,20 +3471,20 @@ dependencies = [ [[package]] name = "node-rpc-client" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "env_logger 0.7.1", "futures 0.1.29", "hyper 0.12.35", "jsonrpc-core-client", - "log 0.4.8", + "log", "node-primitives", "sc-rpc", ] [[package]] name = "node-runtime" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-benchmarking", "frame-executive", @@ -3543,6 +3513,7 @@ dependencies = [ "pallet-randomness-collective-flip", "pallet-recovery", "pallet-session", + "pallet-session-benchmarking", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", @@ -3554,7 +3525,6 @@ dependencies = [ "pallet-utility", "pallet-vesting", "parity-scale-codec", - "rustc-hex", "serde", "sp-api", "sp-authority-discovery", @@ -3576,10 +3546,10 @@ dependencies = [ [[package]] name = "node-template" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "futures 0.3.4", - "log 0.4.8", + "log", "node-template-runtime", "sc-basic-authorship", "sc-cli", @@ -3605,7 +3575,7 @@ dependencies = [ [[package]] name = "node-template-runtime" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-executive", "frame-support", @@ -3637,13 +3607,13 @@ dependencies = [ [[package]] name = "node-testing" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "criterion 0.3.1", "frame-support", "frame-system", "fs_extra", - "log 0.4.8", + "log", "node-executor", "node-primitives", "node-runtime", @@ -3683,9 +3653,9 @@ dependencies = [ [[package]] name = "node-transaction-factory" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ - "log 0.4.8", + "log", "parity-scale-codec", "sc-block-builder", "sc-cli", @@ -3714,12 +3684,12 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "4.2.3" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" dependencies = [ "memchr", - "version_check 0.1.5", + "version_check", ] [[package]] @@ -3743,8 +3713,18 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.42" +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" dependencies = [ @@ -3754,9 +3734,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ "autocfg 1.0.0", "num-bigint", @@ -3771,6 +3751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" dependencies = [ "autocfg 1.0.0", + "libm", ] [[package]] @@ -3828,39 +3809,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -[[package]] -name = "openssl" -version = "0.10.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "lazy_static", - "libc", - "openssl-sys", -] - [[package]] name = "openssl-probe" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -[[package]] -name = "openssl-sys" -version = "0.9.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" -dependencies = [ - "autocfg 1.0.0", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "output_vt100" version = "0.1.2" @@ -3881,7 +3835,7 @@ dependencies = [ [[package]] name = "pallet-assets" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -3895,7 +3849,7 @@ dependencies = [ [[package]] name = "pallet-aura" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -3917,7 +3871,7 @@ dependencies = [ [[package]] name = "pallet-authority-discovery" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -3935,7 +3889,7 @@ dependencies = [ [[package]] name = "pallet-authorship" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -3951,18 +3905,16 @@ dependencies = [ [[package]] name = "pallet-babe" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", - "hex-literal", - "lazy_static", "pallet-session", "pallet-timestamp", "parity-scale-codec", - "parking_lot 0.10.0", "serde", "sp-consensus-babe", + "sp-consensus-vrf", "sp-core", "sp-inherents", "sp-io", @@ -3970,13 +3922,11 @@ dependencies = [ "sp-staking", "sp-std", "sp-timestamp", - "sp-version", - "substrate-test-runtime", ] [[package]] name = "pallet-balances" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-benchmarking", "frame-support", @@ -3992,7 +3942,7 @@ dependencies = [ [[package]] name = "pallet-benchmark" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-benchmarking", "frame-support", @@ -4006,8 +3956,9 @@ dependencies = [ [[package]] name = "pallet-collective" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "hex-literal", @@ -4022,7 +3973,7 @@ dependencies = [ [[package]] name = "pallet-contracts" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "assert_matches", "frame-support", @@ -4047,7 +3998,7 @@ dependencies = [ [[package]] name = "pallet-contracts-primitives" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", "sp-runtime", @@ -4056,7 +4007,7 @@ dependencies = [ [[package]] name = "pallet-contracts-rpc" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "jsonrpc-core", "jsonrpc-core-client", @@ -4075,7 +4026,7 @@ dependencies = [ [[package]] name = "pallet-contracts-rpc-runtime-api" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "pallet-contracts-primitives", "parity-scale-codec", @@ -4086,8 +4037,9 @@ dependencies = [ [[package]] name = "pallet-democracy" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "hex-literal", @@ -4103,7 +4055,7 @@ dependencies = [ [[package]] name = "pallet-elections" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4119,7 +4071,7 @@ dependencies = [ [[package]] name = "pallet-elections-phragmen" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4137,7 +4089,7 @@ dependencies = [ [[package]] name = "pallet-evm" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "evm", "frame-support", @@ -4157,7 +4109,7 @@ dependencies = [ [[package]] name = "pallet-example" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-benchmarking", "frame-support", @@ -4173,13 +4125,13 @@ dependencies = [ [[package]] name = "pallet-example-offchain-worker" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", + "lite-json", "parity-scale-codec", "serde", - "serde_json", "sp-core", "sp-io", "sp-runtime", @@ -4188,7 +4140,7 @@ dependencies = [ [[package]] name = "pallet-finality-tracker" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4205,7 +4157,7 @@ dependencies = [ [[package]] name = "pallet-generic-asset" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4219,7 +4171,7 @@ dependencies = [ [[package]] name = "pallet-grandpa" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4237,7 +4189,7 @@ dependencies = [ [[package]] name = "pallet-identity" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "enumflags2", "frame-benchmarking", @@ -4254,8 +4206,9 @@ dependencies = [ [[package]] name = "pallet-im-online" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "pallet-authorship", @@ -4272,7 +4225,7 @@ dependencies = [ [[package]] name = "pallet-indices" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4288,7 +4241,7 @@ dependencies = [ [[package]] name = "pallet-membership" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4302,7 +4255,7 @@ dependencies = [ [[package]] name = "pallet-nicks" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4317,7 +4270,7 @@ dependencies = [ [[package]] name = "pallet-offences" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4333,7 +4286,7 @@ dependencies = [ [[package]] name = "pallet-randomness-collective-flip" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4347,7 +4300,7 @@ dependencies = [ [[package]] name = "pallet-recovery" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "enumflags2", "frame-support", @@ -4363,7 +4316,7 @@ dependencies = [ [[package]] name = "pallet-scored-pool" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4378,7 +4331,7 @@ dependencies = [ [[package]] name = "pallet-session" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4396,15 +4349,27 @@ dependencies = [ "sp-trie", ] +[[package]] +name = "pallet-session-benchmarking" +version = "2.0.0-alpha.5" +dependencies = [ + "frame-benchmarking", + "frame-system", + "pallet-session", + "pallet-staking", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-society" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", "pallet-balances", "parity-scale-codec", - "rand_chacha 0.2.1", + "rand_chacha 0.2.2", "serde", "sp-core", "sp-io", @@ -4414,30 +4379,38 @@ dependencies = [ [[package]] name = "pallet-staking" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ + "env_logger 0.7.1", + "frame-benchmarking", "frame-support", "frame-system", + "hex", "pallet-authorship", "pallet-balances", + "pallet-indices", "pallet-session", "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", + "parking_lot 0.10.0", + "rand 0.7.3", + "rand_chacha 0.2.2", "serde", + "sp-application-crypto", "sp-core", "sp-io", - "sp-keyring", "sp-phragmen", "sp-runtime", "sp-staking", "sp-std", + "static_assertions", "substrate-test-utils", ] [[package]] name = "pallet-staking-reward-curve" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -4448,7 +4421,7 @@ dependencies = [ [[package]] name = "pallet-sudo" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4462,7 +4435,7 @@ dependencies = [ [[package]] name = "pallet-template" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4475,7 +4448,7 @@ dependencies = [ [[package]] name = "pallet-timestamp" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-benchmarking", "frame-support", @@ -4493,7 +4466,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -4508,7 +4481,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "jsonrpc-core", "jsonrpc-core-client", @@ -4525,7 +4498,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "parity-scale-codec", @@ -4538,8 +4511,9 @@ dependencies = [ [[package]] name = "pallet-treasury" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "pallet-balances", @@ -4553,8 +4527,9 @@ dependencies = [ [[package]] name = "pallet-utility" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "pallet-balances", @@ -4568,7 +4543,7 @@ dependencies = [ [[package]] name = "pallet-vesting" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "enumflags2", "frame-benchmarking", @@ -4585,12 +4560,6 @@ dependencies = [ "sp-storage", ] -[[package]] -name = "parity-bytes" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c276d76c5333b8c2579e02d49a06733a55b8282d2d9b13e8d53b6406bd7e30a" - [[package]] name = "parity-multiaddr" version = "0.7.3" @@ -4626,9 +4595,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f509c5e67ca0605ee17dcd3f91ef41cadd685c75a298fb6261b781a5acb3f910" +checksum = "329c8f7f4244ddb5c37c103641027a76c530e65e8e4b8240b29f81ea40508b17" dependencies = [ "arrayvec 0.5.1", "bitvec", @@ -4657,9 +4626,9 @@ checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" [[package]] name = "parity-util-mem" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9344bc978467339b9ae688f9dcf279d1aaa0ccfc88e9a780c729b765a82d57d5" +checksum = "6e42755f26e5ea21a6a819d9e63cbd70713e9867a2b767ec2cc65ca7659532c5" dependencies = [ "cfg-if", "ethereum-types", @@ -4751,9 +4720,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e1afe738d71b1ebab5f1207c055054015427dbfc7bbe9ee1266894156ec046" +checksum = "092d791bf7847f70bbd49085489fba25fc2c193571752bff9e36e74e72403932" dependencies = [ "paste-impl", "proc-macro-hack", @@ -4761,9 +4730,9 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4dc4a7f6f743211c5aab239640a65091535d97d43d92a52bca435a640892bb" +checksum = "406c23fb4c45cc6f68a9bbabb8ec7bd6f8cfcbd17e9e8f72c2460282f8325729" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -4879,9 +4848,9 @@ checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" [[package]] name = "predicates" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1188bf092c81c18228c383b190c069a8a613c18a046ffa9fdfc0f5fc8fb2da8a" +checksum = "347a1b6f0b21e636bc9872fb60b83b8e185f6f5516298b8238699f7f9a531030" dependencies = [ "difference", "predicates-core", @@ -4917,9 +4886,9 @@ dependencies = [ [[package]] name = "primitive-types" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4336f4f5d5524fa60bcbd6fe626f9223d8142a50e7053e979acdf0da41ab975" +checksum = "e5e4b9943a2da369aec5e96f7c10ebc74fcf434d39590d974b0a3460e6f67fbb" dependencies = [ "fixed-hash", "impl-codec", @@ -4939,52 +4908,47 @@ dependencies = [ [[package]] name = "proc-macro-error" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052b3c9af39c7e5e94245f820530487d19eb285faedcb40e0c3275132293f242" +checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "rustversion", "syn", + "version_check", ] [[package]] name = "proc-macro-error-attr" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d175bef481c7902e63e3165627123fff3502f06ac043d3ef42d08c1246da9253" +checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" dependencies = [ "proc-macro2", "quote", - "rustversion", "syn", "syn-mid", + "version_check", ] [[package]] name = "proc-macro-hack" -version = "0.5.11" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420" [[package]] name = "proc-macro-nested" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" +checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" dependencies = [ "unicode-xid", ] @@ -5022,7 +4986,7 @@ dependencies = [ "bytes 0.5.4", "heck", "itertools", - "log 0.4.8", + "log", "multimap", "petgraph", "prost", @@ -5056,9 +5020,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.10.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6686ddd96a8dbe2687b5f2a687b2cfb520854010ec480f2d74c32e7c9873d3c5" +checksum = "71964f34fd51cf04882d7ae3325fa0794d4cad66a03d0003f38d8ae4f63ba126" [[package]] name = "pwasm-utils" @@ -5067,7 +5031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f7a12f176deee919f4ba55326ee17491c8b707d0987aed822682c821b660192" dependencies = [ "byteorder 1.3.4", - "log 0.4.8", + "log", "parity-wasm 0.41.0", ] @@ -5084,7 +5048,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" dependencies = [ "env_logger 0.7.1", - "log 0.4.8", + "log", "rand 0.7.3", "rand_core 0.5.1", ] @@ -5109,6 +5073,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" + [[package]] name = "rand" version = "0.3.23" @@ -5132,6 +5102,19 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "rand" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "winapi 0.3.8", +] + [[package]] name = "rand" version = "0.6.5" @@ -5159,7 +5142,7 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", - "rand_chacha 0.2.1", + "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", ] @@ -5176,11 +5159,11 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "c2-chacha", + "ppv-lite86", "rand_core 0.5.1", ] @@ -5301,6 +5284,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.3.0" @@ -5353,9 +5342,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.3.4" +version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" +checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" dependencies = [ "aho-corasick", "memchr", @@ -5365,18 +5354,18 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" dependencies = [ "byteorder 1.3.4", ] [[package]] name = "regex-syntax" -version = "0.6.14" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b28dfe3fe9badec5dbf0a79a9cccad2cfc2ab5484bdb3e44cbd1ae8b3ba2be06" +checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" [[package]] name = "region" @@ -5401,9 +5390,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.11" +version = "0.16.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "741ba1704ae21999c00942f9f5944f801e977f54302af346b596287599ad1862" +checksum = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c" dependencies = [ "cc", "lazy_static", @@ -5416,9 +5405,9 @@ dependencies = [ [[package]] name = "rlp" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a44d5ae8afcb238af8b75640907edc6c931efcfab2c854e81ed35fa080f84cd" +checksum = "4a7d3f9bed94764eac15b8f14af59fac420c236adaff743b7bcc88e265cb4345" dependencies = [ "rustc-hex", ] @@ -5489,7 +5478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" dependencies = [ "base64 0.10.1", - "log 0.4.8", + "log", "ring", "sct", "webpki", @@ -5502,7 +5491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" dependencies = [ "base64 0.11.0", - "log 0.4.8", + "log", "ring", "sct", "webpki", @@ -5517,7 +5506,7 @@ dependencies = [ "openssl-probe", "rustls 0.17.0", "schannel", - "security-framework 0.4.1", + "security-framework", ] [[package]] @@ -5544,9 +5533,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" [[package]] name = "safe-mix" @@ -5557,12 +5546,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "salsa20" version = "0.3.0" @@ -5594,7 +5577,7 @@ dependencies = [ [[package]] name = "sc-authority-discovery" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "bytes 0.5.4", "derive_more", @@ -5602,7 +5585,7 @@ dependencies = [ "futures 0.3.4", "futures-timer 3.0.2", "libp2p", - "log 0.4.8", + "log", "parity-scale-codec", "prost", "prost-build", @@ -5624,10 +5607,11 @@ dependencies = [ [[package]] name = "sc-basic-authorship" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "futures 0.3.4", - "log 0.4.8", + "futures-timer 3.0.2", + "log", "parity-scale-codec", "parking_lot 0.10.0", "sc-block-builder", @@ -5647,7 +5631,7 @@ dependencies = [ [[package]] name = "sc-block-builder" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "parity-scale-codec", "sc-client-api", @@ -5664,7 +5648,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "impl-trait-for-tuples", "sc-chain-spec-derive", @@ -5678,7 +5662,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -5688,7 +5672,7 @@ dependencies = [ [[package]] name = "sc-cli" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "ansi_term 0.12.1", "app_dirs", @@ -5700,8 +5684,9 @@ dependencies = [ "fdlimit", "futures 0.3.4", "lazy_static", - "log 0.4.8", + "log", "names", + "nix", "parity-util-mem", "regex", "rpassword", @@ -5722,12 +5707,12 @@ dependencies = [ "substrate-prometheus-endpoint", "tempfile", "time", - "tokio 0.2.12", + "tokio 0.2.13", ] [[package]] name = "sc-client" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "derive_more", "env_logger 0.7.1", @@ -5737,7 +5722,7 @@ dependencies = [ "hex-literal", "kvdb", "kvdb-memorydb", - "log 0.4.8", + "log", "parity-scale-codec", "parking_lot 0.10.0", "sc-block-builder", @@ -5765,7 +5750,7 @@ dependencies = [ [[package]] name = "sc-client-api" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "derive_more", "fnv", @@ -5773,7 +5758,7 @@ dependencies = [ "hash-db", "hex-literal", "kvdb", - "log 0.4.8", + "log", "parity-scale-codec", "parking_lot 0.10.0", "sc-executor", @@ -5797,7 +5782,7 @@ dependencies = [ [[package]] name = "sc-client-db" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "env_logger 0.7.1", "hash-db", @@ -5805,7 +5790,7 @@ dependencies = [ "kvdb-memorydb", "kvdb-rocksdb", "linked-hash-map", - "log 0.4.8", + "log", "parity-scale-codec", "parity-util-mem", "parking_lot 0.10.0", @@ -5829,13 +5814,13 @@ dependencies = [ [[package]] name = "sc-consensus-aura" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "derive_more", "env_logger 0.7.1", "futures 0.3.4", "futures-timer 3.0.2", - "log 0.4.8", + "log", "parity-scale-codec", "parking_lot 0.10.0", "sc-block-builder", @@ -5867,14 +5852,14 @@ dependencies = [ [[package]] name = "sc-consensus-babe" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "derive_more", "env_logger 0.7.1", "fork-tree", "futures 0.3.4", "futures-timer 3.0.2", - "log 0.4.8", + "log", "merlin", "num-bigint", "num-rational", @@ -5903,6 +5888,7 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-consensus-babe", + "sp-consensus-vrf", "sp-core", "sp-inherents", "sp-io", @@ -5916,7 +5902,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe-rpc" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "derive_more", "futures 0.3.4", @@ -5941,7 +5927,7 @@ dependencies = [ [[package]] name = "sc-consensus-epochs" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "fork-tree", "parity-scale-codec", @@ -5953,15 +5939,16 @@ dependencies = [ [[package]] name = "sc-consensus-manual-seal" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ + "assert_matches", "derive_more", "env_logger 0.7.1", "futures 0.3.4", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "log 0.4.8", + "log", "parking_lot 0.10.0", "sc-basic-authorship", "sc-client", @@ -5976,16 +5963,16 @@ dependencies = [ "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", "tempfile", - "tokio 0.2.12", + "tokio 0.2.13", ] [[package]] name = "sc-consensus-pow" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "derive_more", "futures 0.3.4", - "log 0.4.8", + "log", "parity-scale-codec", "sc-client-api", "sp-api", @@ -6001,11 +5988,11 @@ dependencies = [ [[package]] name = "sc-consensus-slots" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "futures 0.3.4", "futures-timer 3.0.2", - "log 0.4.8", + "log", "parity-scale-codec", "parking_lot 0.10.0", "sc-client-api", @@ -6022,9 +6009,9 @@ dependencies = [ [[package]] name = "sc-consensus-uncles" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ - "log 0.4.8", + "log", "sc-client-api", "sp-authorship", "sp-consensus", @@ -6035,14 +6022,14 @@ dependencies = [ [[package]] name = "sc-executor" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "assert_matches", "derive_more", "hex-literal", "lazy_static", "libsecp256k1", - "log 0.4.8", + "log", "parity-scale-codec", "parity-wasm 0.41.0", "parking_lot 0.10.0", @@ -6069,10 +6056,10 @@ dependencies = [ [[package]] name = "sc-executor-common" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "derive_more", - "log 0.4.8", + "log", "parity-scale-codec", "sp-allocator", "sp-core", @@ -6084,9 +6071,9 @@ dependencies = [ [[package]] name = "sc-executor-wasmi" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ - "log 0.4.8", + "log", "parity-scale-codec", "parity-wasm 0.41.0", "sc-executor-common", @@ -6099,10 +6086,10 @@ dependencies = [ [[package]] name = "sc-executor-wasmtime" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "assert_matches", - "log 0.4.8", + "log", "parity-scale-codec", "parity-wasm 0.41.0", "sc-executor-common", @@ -6111,12 +6098,12 @@ dependencies = [ "sp-core", "sp-runtime-interface", "sp-wasm-interface", - "wasmtime", + "substrate-wasmtime", ] [[package]] name = "sc-finality-grandpa" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "assert_matches", "env_logger 0.7.1", @@ -6124,7 +6111,7 @@ dependencies = [ "fork-tree", "futures 0.3.4", "futures-timer 3.0.2", - "log 0.4.8", + "log", "parity-scale-codec", "parking_lot 0.10.0", "pin-project", @@ -6153,16 +6140,16 @@ dependencies = [ "substrate-prometheus-endpoint", "substrate-test-runtime-client", "tempfile", - "tokio 0.2.12", + "tokio 0.2.13", ] [[package]] name = "sc-informant" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "ansi_term 0.12.1", "futures 0.3.4", - "log 0.4.8", + "log", "parity-util-mem", "sc-client-api", "sc-network", @@ -6174,7 +6161,7 @@ dependencies = [ [[package]] name = "sc-keystore" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "derive_more", "hex", @@ -6189,7 +6176,7 @@ dependencies = [ [[package]] name = "sc-network" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "assert_matches", "async-std", @@ -6204,10 +6191,11 @@ dependencies = [ "futures 0.3.4", "futures-timer 3.0.2", "futures_codec", + "hex", "libp2p", "linked-hash-map", "linked_hash_set", - "log 0.4.8", + "log", "lru", "nohash-hasher", "parity-scale-codec", @@ -6217,7 +6205,6 @@ dependencies = [ "prost-build", "quickcheck", "rand 0.7.3", - "rustc-hex", "sc-block-builder", "sc-client", "sc-client-api", @@ -6243,20 +6230,21 @@ dependencies = [ "unsigned-varint", "void", "wasm-timer", - "zeroize 1.1.0", + "zeroize", ] [[package]] name = "sc-network-gossip" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "futures 0.3.4", "futures-timer 3.0.2", "libp2p", - "log 0.4.8", + "log", "lru", "sc-network", "sp-runtime", + "substrate-test-runtime-client", "wasm-timer", ] @@ -6268,7 +6256,7 @@ dependencies = [ "futures 0.3.4", "futures-timer 3.0.2", "libp2p", - "log 0.4.8", + "log", "parking_lot 0.10.0", "rand 0.7.3", "sc-block-builder", @@ -6287,7 +6275,7 @@ dependencies = [ [[package]] name = "sc-offchain" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "bytes 0.5.4", "env_logger 0.7.1", @@ -6295,9 +6283,9 @@ dependencies = [ "fnv", "futures 0.3.4", "futures-timer 3.0.2", - "hyper 0.13.2", + "hyper 0.13.4", "hyper-rustls", - "log 0.4.8", + "log", "num_cpus", "parity-scale-codec", "parking_lot 0.10.0", @@ -6314,16 +6302,16 @@ dependencies = [ "sp-transaction-pool", "substrate-test-runtime-client", "threadpool", - "tokio 0.2.12", + "tokio 0.2.13", ] [[package]] name = "sc-peerset" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "futures 0.3.4", "libp2p", - "log 0.4.8", + "log", "rand 0.7.3", "serde_json", "wasm-timer", @@ -6331,7 +6319,7 @@ dependencies = [ [[package]] name = "sc-rpc" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "assert_matches", "futures 0.1.29", @@ -6339,10 +6327,9 @@ dependencies = [ "hash-db", "jsonrpc-core", "jsonrpc-pubsub", - "log 0.4.8", + "log", "parity-scale-codec", "parking_lot 0.10.0", - "rustc-hex", "sc-block-builder", "sc-client", "sc-client-api", @@ -6369,7 +6356,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "derive_more", "futures 0.3.4", @@ -6377,7 +6364,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "jsonrpc-pubsub", - "log 0.4.8", + "log", "parity-scale-codec", "parking_lot 0.10.0", "serde", @@ -6391,13 +6378,13 @@ dependencies = [ [[package]] name = "sc-rpc-server" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "jsonrpc-core", "jsonrpc-http-server", "jsonrpc-pubsub", "jsonrpc-ws-server", - "log 0.4.8", + "log", "serde", "serde_json", "sp-runtime", @@ -6418,7 +6405,7 @@ dependencies = [ [[package]] name = "sc-service" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "derive_more", "exit-future", @@ -6427,7 +6414,7 @@ dependencies = [ "futures-diagnose", "futures-timer 3.0.2", "lazy_static", - "log 0.4.8", + "log", "parity-multiaddr", "parity-scale-codec", "parity-util-mem", @@ -6476,7 +6463,7 @@ dependencies = [ "fdlimit", "futures 0.1.29", "futures 0.3.4", - "log 0.4.8", + "log", "sc-client", "sc-network", "sc-service", @@ -6490,10 +6477,10 @@ dependencies = [ [[package]] name = "sc-state-db" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "env_logger 0.7.1", - "log 0.4.8", + "log", "parity-scale-codec", "parity-util-mem", "parity-util-mem-derive", @@ -6504,13 +6491,13 @@ dependencies = [ [[package]] name = "sc-telemetry" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "bytes 0.5.4", "futures 0.3.4", "futures-timer 3.0.2", "libp2p", - "log 0.4.8", + "log", "parking_lot 0.10.0", "pin-project", "rand 0.7.3", @@ -6525,10 +6512,10 @@ dependencies = [ [[package]] name = "sc-tracing" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "erased-serde", - "log 0.4.8", + "log", "parking_lot 0.10.0", "sc-telemetry", "serde", @@ -6540,14 +6527,14 @@ dependencies = [ [[package]] name = "sc-transaction-graph" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "assert_matches", "criterion 0.3.1", "derive_more", "futures 0.3.4", "linked-hash-map", - "log 0.4.8", + "log", "parity-scale-codec", "parity-util-mem", "parking_lot 0.10.0", @@ -6562,13 +6549,15 @@ dependencies = [ [[package]] name = "sc-transaction-pool" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ + "assert_matches", "derive_more", "futures 0.3.4", "futures-diagnose", - "futures-timer 2.0.2", - "log 0.4.8", + "hex", + "intervalier", + "log", "parity-scale-codec", "parity-util-mem", "parking_lot 0.10.0", @@ -6587,9 +6576,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295" +checksum = "039c25b130bd8c1321ee2d7de7fde2659fa9c2744e4bb29711cfc852ea53cd19" dependencies = [ "lazy_static", "winapi 0.3.8", @@ -6597,19 +6586,20 @@ dependencies = [ [[package]] name = "schnorrkel" -version = "0.8.5" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eacd8381b3c37840c9c9f40472af529e49975bdcbc24f83c31059fd6539023d3" +checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" dependencies = [ - "curve25519-dalek 1.2.3", - "failure", + "arrayref", + "arrayvec 0.5.1", + "curve25519-dalek", + "getrandom", "merlin", - "rand 0.6.5", - "rand_core 0.4.2", - "rand_os", + "rand 0.7.3", + "rand_core 0.5.1", "sha2", "subtle 2.2.2", - "zeroize 0.9.3", + "zeroize", ] [[package]] @@ -6656,44 +6646,24 @@ dependencies = [ [[package]] name = "security-framework" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" -dependencies = [ - "core-foundation 0.6.4", - "core-foundation-sys 0.6.2", - "libc", - "security-framework-sys 0.3.3", -] - -[[package]] -name = "security-framework" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97bbedbe81904398b6ebb054b3e912f99d55807125790f3198ac990d98def5b0" +checksum = "572dfa3a0785509e7a44b5b4bebcf94d41ba34e9ed9eb9df722545c3b3c4144a" dependencies = [ "bitflags", - "core-foundation 0.7.0", - "core-foundation-sys 0.7.0", - "security-framework-sys 0.4.1", -] - -[[package]] -name = "security-framework-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" -dependencies = [ - "core-foundation-sys 0.6.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06fd2f23e31ef68dd2328cc383bd493142e46107a3a0e24f7d734e3f3b80fe4c" +checksum = "8ddb15a5fec93b7021b8a9e96009c5d8d51c15673569f7c0f6b7204e5b7b404f" dependencies = [ - "core-foundation-sys 0.7.0", + "core-foundation-sys", "libc", ] @@ -6742,18 +6712,18 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" dependencies = [ "proc-macro2", "quote", @@ -6762,9 +6732,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" +checksum = "78a7a12c167809363ec3bd7329fc0a3369056996de43c4b37ef3cd54a6ce4867" dependencies = [ "itoa", "ryu", @@ -6933,9 +6903,9 @@ dependencies = [ "bytes 0.5.4", "flate2", "futures 0.3.4", - "http 0.2.0", + "http 0.2.1", "httparse", - "log 0.4.8", + "log", "rand 0.7.3", "sha1", "smallvec 1.2.0", @@ -6943,18 +6913,12 @@ dependencies = [ "thiserror", ] -[[package]] -name = "sourcefile" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" - [[package]] name = "sp-allocator" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "derive_more", - "log 0.4.8", + "log", "sp-core", "sp-std", "sp-wasm-interface", @@ -6962,7 +6926,7 @@ dependencies = [ [[package]] name = "sp-api" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "hash-db", "parity-scale-codec", @@ -6977,7 +6941,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "blake2-rfc", "proc-macro-crate", @@ -7007,7 +6971,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", "serde", @@ -7029,7 +6993,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "criterion 0.3.1", "integer-sqrt", @@ -7042,9 +7006,20 @@ dependencies = [ "sp-std", ] +[[package]] +name = "sp-arithmetic-fuzzer" +version = "2.0.0-alpha.5" +dependencies = [ + "honggfuzz", + "num-bigint", + "num-traits", + "primitive-types", + "sp-arithmetic", +] + [[package]] name = "sp-authority-discovery" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", "sp-api", @@ -7055,7 +7030,7 @@ dependencies = [ [[package]] name = "sp-authorship" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", "sp-inherents", @@ -7065,7 +7040,7 @@ dependencies = [ [[package]] name = "sp-block-builder" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", "sp-api", @@ -7076,10 +7051,10 @@ dependencies = [ [[package]] name = "sp-blockchain" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "derive_more", - "log 0.4.8", + "log", "lru", "parity-scale-codec", "parking_lot 0.10.0", @@ -7091,14 +7066,14 @@ dependencies = [ [[package]] name = "sp-consensus" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "derive_more", "futures 0.3.4", "futures-diagnose", "futures-timer 3.0.2", "libp2p", - "log 0.4.8", + "log", "parity-scale-codec", "parking_lot 0.10.0", "serde", @@ -7113,7 +7088,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "parity-scale-codec", "sp-api", @@ -7126,13 +7101,13 @@ dependencies = [ [[package]] name = "sp-consensus-babe" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "parity-scale-codec", - "schnorrkel", "sp-api", "sp-application-crypto", "sp-consensus", + "sp-consensus-vrf", "sp-inherents", "sp-runtime", "sp-std", @@ -7141,7 +7116,7 @@ dependencies = [ [[package]] name = "sp-consensus-pow" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "parity-scale-codec", "sp-api", @@ -7150,9 +7125,20 @@ dependencies = [ "sp-std", ] +[[package]] +name = "sp-consensus-vrf" +version = "0.8.0-alpha.5" +dependencies = [ + "parity-scale-codec", + "schnorrkel", + "sp-core", + "sp-runtime", + "sp-std", +] + [[package]] name = "sp-core" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "base58", "blake2-rfc", @@ -7167,7 +7153,7 @@ dependencies = [ "impl-serde 0.3.0", "lazy_static", "libsecp256k1", - "log 0.4.8", + "log", "num-traits", "parity-scale-codec", "parity-util-mem", @@ -7176,7 +7162,6 @@ dependencies = [ "primitive-types", "rand 0.7.3", "regex", - "rustc-hex", "schnorrkel", "serde", "serde_json", @@ -7192,12 +7177,12 @@ dependencies = [ "tiny-keccak 2.0.1", "twox-hash", "wasmi", - "zeroize 1.1.0", + "zeroize", ] [[package]] name = "sp-debug-derive" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "proc-macro2", "quote", @@ -7206,7 +7191,7 @@ dependencies = [ [[package]] name = "sp-externalities" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "environmental", "sp-std", @@ -7215,7 +7200,7 @@ dependencies = [ [[package]] name = "sp-finality-grandpa" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", "serde", @@ -7227,7 +7212,7 @@ dependencies = [ [[package]] name = "sp-finality-tracker" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", "sp-inherents", @@ -7236,7 +7221,7 @@ dependencies = [ [[package]] name = "sp-inherents" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "derive_more", "parity-scale-codec", @@ -7247,11 +7232,11 @@ dependencies = [ [[package]] name = "sp-io" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "hash-db", "libsecp256k1", - "log 0.4.8", + "log", "parity-scale-codec", "sp-core", "sp-externalities", @@ -7264,7 +7249,7 @@ dependencies = [ [[package]] name = "sp-keyring" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "lazy_static", "sp-core", @@ -7274,7 +7259,7 @@ dependencies = [ [[package]] name = "sp-offchain" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "sp-api", "sp-runtime", @@ -7282,27 +7267,39 @@ dependencies = [ [[package]] name = "sp-panic-handler" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "backtrace", - "log 0.4.8", + "log", ] [[package]] name = "sp-phragmen" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ + "parity-scale-codec", "rand 0.7.3", "serde", - "sp-io", + "sp-phragmen", + "sp-phragmen-compact", "sp-runtime", "sp-std", "substrate-test-utils", ] +[[package]] +name = "sp-phragmen-compact" +version = "2.0.0-dev" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sp-rpc" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "serde", "serde_json", @@ -7311,11 +7308,11 @@ dependencies = [ [[package]] name = "sp-runtime" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "hash256-std-hasher", "impl-trait-for-tuples", - "log 0.4.8", + "log", "parity-scale-codec", "parity-util-mem", "paste", @@ -7332,7 +7329,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", "primitive-types", @@ -7351,7 +7348,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "Inflector", "proc-macro-crate", @@ -7369,6 +7366,7 @@ dependencies = [ "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-test-wasm", + "sp-runtime-interface-test-wasm-deprecated", "sp-state-machine", ] @@ -7383,9 +7381,20 @@ dependencies = [ "substrate-wasm-builder-runner", ] +[[package]] +name = "sp-runtime-interface-test-wasm-deprecated" +version = "2.0.0-dev" +dependencies = [ + "sp-core", + "sp-io", + "sp-runtime-interface", + "sp-std", + "substrate-wasm-builder-runner", +] + [[package]] name = "sp-sandbox" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "assert_matches", "parity-scale-codec", @@ -7399,7 +7408,7 @@ dependencies = [ [[package]] name = "sp-serializer" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "serde", "serde_json", @@ -7407,7 +7416,7 @@ dependencies = [ [[package]] name = "sp-session" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "sp-api", "sp-core", @@ -7417,7 +7426,7 @@ dependencies = [ [[package]] name = "sp-staking" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "parity-scale-codec", "sp-runtime", @@ -7426,11 +7435,11 @@ dependencies = [ [[package]] name = "sp-state-machine" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "hash-db", "hex-literal", - "log 0.4.8", + "log", "num-traits", "parity-scale-codec", "parking_lot 0.10.0", @@ -7446,11 +7455,11 @@ dependencies = [ [[package]] name = "sp-std" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" [[package]] name = "sp-storage" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "impl-serde 0.2.3", "serde", @@ -7472,7 +7481,7 @@ dependencies = [ [[package]] name = "sp-timestamp" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -7485,11 +7494,11 @@ dependencies = [ [[package]] name = "sp-transaction-pool" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "derive_more", "futures 0.3.4", - "log 0.4.8", + "log", "parity-scale-codec", "serde", "sp-api", @@ -7498,7 +7507,7 @@ dependencies = [ [[package]] name = "sp-trie" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "criterion 0.2.11", "hash-db", @@ -7516,7 +7525,7 @@ dependencies = [ [[package]] name = "sp-version" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "impl-serde 0.2.3", "parity-scale-codec", @@ -7527,7 +7536,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -7553,6 +7562,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "statrs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10102ac8d55e35db2b3fafc26f81ba8647da2e15879ab686a67e6d19af2685e8" +dependencies = [ + "rand 0.5.6", +] + [[package]] name = "stream-cipher" version = "0.3.2" @@ -7588,9 +7606,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.9" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1bcbed7d48956fcbb5d80c6b95aedb553513de0a1b451ea92679d999c010e98" +checksum = "c8faa2719539bbe9d77869bfb15d4ee769f99525e707931452c97b693b3f159d" dependencies = [ "clap", "lazy_static", @@ -7599,9 +7617,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "095064aa1f5b94d14e635d0a5684cf140c43ae40a0fd990708d38f5d669e5f64" +checksum = "3f88b8e18c69496aad6f9ddf4630dd7d585bcaf765786cb415b9aec2fe5a0430" dependencies = [ "heck", "proc-macro-error", @@ -7633,7 +7651,7 @@ dependencies = [ [[package]] name = "subkey" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "clap", "derive_more", @@ -7652,7 +7670,6 @@ dependencies = [ "parity-scale-codec", "rand 0.7.3", "rpassword", - "rustc-hex", "sc-rpc", "serde_json", "sp-core", @@ -7663,9 +7680,9 @@ dependencies = [ [[package]] name = "substrate-bip39" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be511be555a3633e71739a79e4ddff6a6aaa6579fa6114182a51d72c3eb93c5" +checksum = "c004e8166d6e0aa3a9d5fa673e5b7098ff25f930de1013a21341988151e681bb" dependencies = [ "hmac", "pbkdf2", @@ -7675,7 +7692,7 @@ dependencies = [ [[package]] name = "substrate-browser-utils" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "chrono", "clear_on_drop", @@ -7687,7 +7704,7 @@ dependencies = [ "js-sys", "kvdb-web", "libp2p-wasm-ext", - "log 0.4.8", + "log", "rand 0.6.5", "rand 0.7.3", "sc-chain-spec", @@ -7700,11 +7717,11 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" [[package]] name = "substrate-frame-rpc-support" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", @@ -7715,12 +7732,12 @@ dependencies = [ "sc-rpc-api", "serde", "sp-storage", - "tokio 0.2.12", + "tokio 0.2.13", ] [[package]] name = "substrate-frame-rpc-system" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" dependencies = [ "env_logger 0.7.1", "frame-system-rpc-runtime-api", @@ -7728,7 +7745,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "log 0.4.8", + "log", "parity-scale-codec", "sc-client", "sc-transaction-pool", @@ -7743,15 +7760,15 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" dependencies = [ "async-std", "derive_more", "futures-util", - "hyper 0.13.2", - "log 0.4.8", + "hyper 0.13.4", + "log", "prometheus", - "tokio 0.2.12", + "tokio 0.2.13", ] [[package]] @@ -7782,7 +7799,7 @@ dependencies = [ "frame-support", "frame-system", "frame-system-rpc-runtime-api", - "log 0.4.8", + "log", "memory-db", "pallet-babe", "pallet-timestamp", @@ -7849,7 +7866,7 @@ dependencies = [ [[package]] name = "substrate-test-utils" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" [[package]] name = "substrate-wasm-builder" @@ -7870,6 +7887,75 @@ dependencies = [ name = "substrate-wasm-builder-runner" version = "1.0.5" +[[package]] +name = "substrate-wasmtime" +version = "0.13.0-threadsafe.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e512629525ecfe43bffe1f3d9e6bb0f08bf01155288ef27fcaae4ea086e4a9d" +dependencies = [ + "anyhow", + "backtrace", + "cfg-if", + "lazy_static", + "libc", + "region", + "rustc-demangle", + "substrate-wasmtime-jit", + "substrate-wasmtime-runtime", + "target-lexicon", + "wasmparser", + "wasmtime-environ", + "wasmtime-profiling", + "wat", + "winapi 0.3.8", +] + +[[package]] +name = "substrate-wasmtime-jit" +version = "0.13.0-threadsafe.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20de5564886d2bcffdd351c9cd114ceb50758aa58eac3cedb14faabf7f93b91" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "more-asserts", + "region", + "substrate-wasmtime-runtime", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-debug", + "wasmtime-environ", + "wasmtime-profiling", + "winapi 0.3.8", +] + +[[package]] +name = "substrate-wasmtime-runtime" +version = "0.13.0-threadsafe.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d08846f04293a7fc27eeb30f06262ca2e1b4ee20f5192cec1f3ce201e08ceb8" +dependencies = [ + "backtrace", + "cc", + "cfg-if", + "indexmap", + "lazy_static", + "libc", + "memoffset", + "more-asserts", + "region", + "thiserror", + "wasmtime-environ", + "wasmtime-profiling", + "winapi 0.3.8", +] + [[package]] name = "subtle" version = "1.0.0" @@ -7884,9 +7970,9 @@ checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" [[package]] name = "syn" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" dependencies = [ "proc-macro2", "quote", @@ -7918,9 +8004,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.11.7" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e15d793f059727ad34a9245503c13b38262bb32e9906d8122ca64d6ca54b0858" +checksum = "7ccb41798287e8e299a701b5560d886d6ca2c3e7115e9ea2cb68c123aec339b7" dependencies = [ "cfg-if", "doc-comment", @@ -7982,7 +8068,7 @@ dependencies = [ "proc-macro2", "quote", "syn", - "version_check 0.9.1", + "version_check", ] [[package]] @@ -7996,18 +8082,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db" +checksum = "f0570dc61221295909abdb95c739f2e74325e14293b2026b0a7e195091ec54ae" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4" +checksum = "227362df41d566be41a28f64401e07a043157c21c14b9785a0d8e256f940a8fd" dependencies = [ "proc-macro2", "quote", @@ -8045,9 +8131,9 @@ dependencies = [ [[package]] name = "tiny-bip39" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6848cd8f566953ce1e8faeba12ee23cbdbb0437754792cd857d44628b5685e3" +checksum = "b0165e045cc2ae1660270ca65e1676dbaab60feb0f91b10f7d0665e9b47e31f2" dependencies = [ "failure", "hmac", @@ -8113,12 +8199,13 @@ dependencies = [ [[package]] name = "tokio" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34bee1facdc352fba10c9c58b654e6ecb6a2250167772bf86071f7c5f2f5061" +checksum = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616" dependencies = [ "bytes 0.5.4", "fnv", + "futures-core", "iovec", "lazy_static", "libc", @@ -8205,7 +8292,7 @@ checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ "bytes 0.4.12", "futures 0.1.29", - "log 0.4.8", + "log", ] [[package]] @@ -8228,7 +8315,7 @@ dependencies = [ "crossbeam-utils", "futures 0.1.29", "lazy_static", - "log 0.4.8", + "log", "mio", "num_cpus", "parking_lot 0.9.0", @@ -8246,7 +8333,7 @@ checksum = "4adb8b3e5f86b707f1b54e7c15b6de52617a823608ccda98a15d3a24222f265a" dependencies = [ "futures-core", "rustls 0.17.0", - "tokio 0.2.12", + "tokio 0.2.13", "webpki", ] @@ -8296,7 +8383,7 @@ dependencies = [ "crossbeam-utils", "futures 0.1.29", "lazy_static", - "log 0.4.8", + "log", "num_cpus", "slab", "tokio-executor 0.1.10", @@ -8314,17 +8401,6 @@ dependencies = [ "tokio-executor 0.1.10", ] -[[package]] -name = "tokio-tls" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "354b8cd83825b3c20217a9dc174d6a0c67441a2fae5c41bcb1ea6679f6ae0f7c" -dependencies = [ - "futures 0.1.29", - "native-tls", - "tokio-io", -] - [[package]] name = "tokio-udp" version = "0.1.6" @@ -8333,7 +8409,7 @@ checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82" dependencies = [ "bytes 0.4.12", "futures 0.1.29", - "log 0.4.8", + "log", "mio", "tokio-codec", "tokio-io", @@ -8350,7 +8426,7 @@ dependencies = [ "futures 0.1.29", "iovec", "libc", - "log 0.4.8", + "log", "mio", "mio-uds", "tokio-codec", @@ -8360,16 +8436,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" dependencies = [ "bytes 0.5.4", "futures-core", "futures-sink", - "log 0.4.8", + "log", "pin-project-lite", - "tokio 0.2.12", + "tokio 0.2.13", ] [[package]] @@ -8417,12 +8493,6 @@ dependencies = [ "lazy_static", ] -[[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" @@ -8431,9 +8501,9 @@ checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" [[package]] name = "trie-bench" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dcd9bac85703d8f974ee1e6dfe668784b105d3385c174ad729adb7427ad5d81" +checksum = "f105ed33e42b534284b691e804e909c42a8898afcf22896a32255c05a1a50488" dependencies = [ "criterion 0.2.11", "hash-db", @@ -8453,7 +8523,7 @@ checksum = "de9222c50cc325855621271157c973da27a0dcd26fa06f8edf81020bd2333df0" dependencies = [ "hash-db", "hashbrown", - "log 0.4.8", + "log", "rustc-hex", "smallvec 1.2.0", ] @@ -8485,9 +8555,9 @@ checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" [[package]] name = "trybuild" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ff1b18659a2218332848d76ad1c867ce4c6ee37b085e6bc8de9a6d11401220" +checksum = "24b4e093c5ed1a60b22557090120aa14f90ca801549c0949d775ea07c1407720" dependencies = [ "glob 0.3.0", "lazy_static", @@ -8514,15 +8584,9 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56" dependencies = [ - "rand 0.7.3", + "rand 0.3.23", ] -[[package]] -name = "typeable" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" - [[package]] name = "typenum" version = "1.11.2" @@ -8541,22 +8605,13 @@ 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.1", + "version_check", ] [[package]] @@ -8597,12 +8652,13 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "unsigned-varint" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7ffb36714206d2f5f05d61a2bc350415c642f2c54433f0ebf829afbe41d570" +checksum = "f38e01ad4b98f042e166c1bf9a13f9873a99d79eaa171ce7ca81e6dd0f895d8a" dependencies = [ "bytes 0.5.4", - "futures 0.3.4", + "futures-io", + "futures-util", "futures_codec", ] @@ -8654,21 +8710,14 @@ checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" [[package]] name = "vergen" -version = "3.0.4" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aba5e34f93dc7051dfad05b98a18e9156f27e7b431fe1d2398cb6061c0a1dba" +checksum = "4ce50d8996df1f85af15f2cd8d33daae6e479575123ef4314a51a70a230739cb" dependencies = [ "bitflags", "chrono", - "failure", ] -[[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.1" @@ -8695,15 +8744,24 @@ dependencies = [ [[package]] name = "wabt-sys" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af5d153dc96aad7dc13ab90835b892c69867948112d95299e522d370c4e13a08" +checksum = "23d7043ebb3e5d96fad7a8d3ca22ee9880748ff8c3e18092cfb2a49d3b8f9084" dependencies = [ "cc", "cmake", "glob 0.2.11", ] +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.1" @@ -8722,7 +8780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" dependencies = [ "futures 0.1.29", - "log 0.4.8", + "log", "try-lock", ] @@ -8732,7 +8790,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.8", + "log", "try-lock", ] @@ -8744,9 +8802,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.58" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5205e9afdf42282b192e2310a5b463a6d1c1d774e30dc3c791ac37ab42d2616c" +checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -8754,13 +8812,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.58" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11cdb95816290b525b32587d76419facd99662a07e59d3cdb560488a819d9a45" +checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" dependencies = [ "bumpalo", "lazy_static", - "log 0.4.8", + "log", "proc-macro2", "quote", "syn", @@ -8769,9 +8827,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bbdd49e3e28b40dec6a9ba8d17798245ce32b019513a845369c641b275135d9" +checksum = "7add542ea1ac7fdaa9dc25e031a6af33b7d63376292bd24140c637d00d1c312a" dependencies = [ "cfg-if", "js-sys", @@ -8781,9 +8839,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.58" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "574094772ce6921576fb6f2e3f7497b8a76273b6db092be18fc48a082de09dc3" +checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8791,9 +8849,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.58" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85031354f25eaebe78bb7db1c3d86140312a911a106b2e29f9cc440ce3e7668" +checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" dependencies = [ "proc-macro2", "quote", @@ -8804,25 +8862,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.58" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e7e61fc929f4c0dddb748b102ebf9f632e2b8d739f2016542b4de2965a9601" - -[[package]] -name = "wasm-bindgen-webidl" -version = "0.2.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef012a0d93fc0432df126a8eaf547b2dce25a8ce9212e1d3cbeef5c11157975d" -dependencies = [ - "anyhow", - "heck", - "log 0.4.8", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "weedle", -] +checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" [[package]] name = "wasm-gc-api" @@ -8830,7 +8872,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0c32691b6c7e6c14e7f8fd55361a9088b507aa49620fcd06c09b3a1082186b9" dependencies = [ - "log 0.4.8", + "log", "parity-wasm 0.32.0", "rustc-demangle", ] @@ -8881,32 +8923,11 @@ version = "0.51.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aeb1956b19469d1c5e63e459d29e7b5aa0f558d9f16fcef09736f8a265e6c10a" -[[package]] -name = "wasmtime" -version = "0.12.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" -dependencies = [ - "anyhow", - "backtrace", - "cfg-if", - "lazy_static", - "libc", - "region", - "rustc-demangle", - "target-lexicon", - "wasmparser", - "wasmtime-environ", - "wasmtime-jit", - "wasmtime-profiling", - "wasmtime-runtime", - "wat", - "winapi 0.3.8", -] - [[package]] name = "wasmtime-debug" version = "0.12.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d3d007436043bf55ec252d2f4dc1d35834157b5e2f148da839ca502e611cfe1" dependencies = [ "anyhow", "faerie", @@ -8921,7 +8942,8 @@ dependencies = [ [[package]] name = "wasmtime-environ" version = "0.12.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f3dea0e60c076dd0da27fa10c821323903c9554c617ed32eaab8e7a7e36c89" dependencies = [ "anyhow", "base64 0.11.0", @@ -8934,7 +8956,7 @@ dependencies = [ "file-per-thread-logger", "indexmap", "libc", - "log 0.4.8", + "log", "more-asserts", "rayon", "serde", @@ -8946,34 +8968,11 @@ dependencies = [ "zstd", ] -[[package]] -name = "wasmtime-jit" -version = "0.12.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" -dependencies = [ - "anyhow", - "cfg-if", - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", - "cranelift-native", - "cranelift-wasm", - "more-asserts", - "region", - "target-lexicon", - "thiserror", - "wasmparser", - "wasmtime-debug", - "wasmtime-environ", - "wasmtime-profiling", - "wasmtime-runtime", - "winapi 0.3.8", -] - [[package]] name = "wasmtime-profiling" version = "0.12.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984d29c8add3381e60d649f4e3e2a501da900fc2d2586e139502eec32fe0ebc8" dependencies = [ "gimli", "goblin", @@ -8985,55 +8984,32 @@ dependencies = [ "target-lexicon", ] -[[package]] -name = "wasmtime-runtime" -version = "0.12.0" -source = "git+https://github.com/paritytech/wasmtime?branch=a-thread-safe-api#851887d84d03543f931f6312448d0dd5d8a9324e" -dependencies = [ - "backtrace", - "cc", - "cfg-if", - "indexmap", - "lazy_static", - "libc", - "memoffset", - "more-asserts", - "region", - "thiserror", - "wasmtime-environ", - "wasmtime-profiling", - "winapi 0.3.8", -] - [[package]] name = "wast" -version = "9.0.0" +version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee7b16105405ca2aa2376ba522d8d4b1a11604941dd3bb7df9fd2ece60f8d16a" +checksum = "0615ba420811bcda39cf80e8a1bd75997aec09222bda35165920a07ef15cc695" dependencies = [ "leb128", ] [[package]] name = "wat" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56173f7f4fb59aebe35a7e71423845e1c6c7144bfb56362d497931b6b3bed0f6" +checksum = "095f615fbfcae695e3a4cea7d9f02f70561c81274c0142f45a12bf1e154d08bd" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.35" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf97caf6aa8c2b1dac90faf0db529d9d63c93846cca4911856f78a83cebf53b" +checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" dependencies = [ - "anyhow", "js-sys", - "sourcefile", "wasm-bindgen", - "wasm-bindgen-webidl", ] [[package]] @@ -9064,61 +9040,11 @@ 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.29", - "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 1.3.4", - "bytes 0.4.12", - "futures 0.1.29", - "native-tls", - "rand 0.6.5", - "sha1", - "tokio-codec", - "tokio-io", - "tokio-tcp", - "tokio-tls", -] - -[[package]] -name = "weedle" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" -dependencies = [ - "nom", -] - [[package]] name = "which" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5475d47078209a02e60614f7ba5e645ef3ed60f771920ac1906d7c1cc65024c8" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" dependencies = [ "libc", ] @@ -9153,9 +9079,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" +checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" dependencies = [ "winapi 0.3.8", ] @@ -9175,7 +9101,7 @@ dependencies = [ "byteorder 1.3.4", "bytes 0.4.12", "httparse", - "log 0.4.8", + "log", "mio", "mio-extras", "rand 0.7.3", @@ -9200,9 +9126,9 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217" dependencies = [ - "curve25519-dalek 2.0.0", + "curve25519-dalek", "rand_core 0.5.1", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -9213,25 +9139,18 @@ checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" [[package]] name = "yamux" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03098897b734bd943ab23f6aa9f98aafd72a88516deedd66f9d564c57bf2f19" +checksum = "84300bb493cc878f3638b981c62b4632ec1a5c52daaa3036651e8c106d3b55ea" dependencies = [ - "bytes 0.5.4", "futures 0.3.4", - "log 0.4.8", + "log", "nohash-hasher", "parking_lot 0.10.0", "rand 0.7.3", - "thiserror", + "static_assertions", ] -[[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" diff --git a/Cargo.toml b/Cargo.toml index 0459bc8ebbd6abd658db6295fefc4c699291207e..39a6ff1f2dcda7360ddb56e11df7d631a04d54ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ members = [ "frame/recovery", "frame/scored-pool", "frame/session", + "frame/session/benchmarking", "frame/society", "frame/staking", "frame/staking/reward-curve", @@ -117,6 +118,7 @@ members = [ "primitives/consensus/babe", "primitives/consensus/common", "primitives/consensus/pow", + "primitives/consensus/vrf", "primitives/core", "primitives/debug-derive", "primitives/storage", @@ -128,10 +130,12 @@ members = [ "primitives/offchain", "primitives/panic-handler", "primitives/phragmen", + "primitives/phragmen/compact", "primitives/rpc", "primitives/runtime-interface", "primitives/runtime-interface/proc-macro", "primitives/runtime-interface/test-wasm", + "primitives/runtime-interface/test-wasm-deprecated", "primitives/runtime-interface/test", "primitives/serializer", "primitives/session", @@ -139,6 +143,7 @@ members = [ "primitives/api/proc-macro", "primitives/api/test", "primitives/arithmetic", + "primitives/arithmetic/fuzzer", "primitives/io", "primitives/runtime", "primitives/sandbox", diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index 1e8c3fad2e3c0e2f1a8cef0f2d7fff57f2b45ff3..b312d311118026092cc11b338b5c47dcf4428fc3 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-template" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Anonymous"] edition = "2018" license = "Unlicense" @@ -12,30 +12,33 @@ repository = "https://github.com/paritytech/substrate/" name = "node-template" [dependencies] -futures = "0.3.1" +futures = "0.3.4" log = "0.4.8" structopt = "0.3.8" -sc-cli = { version = "0.8.0-alpha.2", path = "../../../client/cli" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sc-executor = { version = "0.8.0-alpha.2", path = "../../../client/executor" } -sc-service = { version = "0.8.0-alpha.2", path = "../../../client/service" } -sp-inherents = { version = "2.0.0-alpha.2", path = "../../../primitives/inherents" } -sc-transaction-pool = { version = "2.0.0-alpha.2", path = "../../../client/transaction-pool" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../../primitives/transaction-pool" } -sc-network = { version = "0.8.0-alpha.2", path = "../../../client/network" } -sc-consensus-aura = { version = "0.8.0-alpha.2", path = "../../../client/consensus/aura" } -sp-consensus-aura = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/aura" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } -grandpa = { version = "0.8.0-alpha.2", package = "sc-finality-grandpa", path = "../../../client/finality-grandpa" } -grandpa-primitives = { version = "2.0.0-alpha.2", package = "sp-finality-grandpa", path = "../../../primitives/finality-grandpa" } -sc-client = { version = "0.8.0-alpha.2", path = "../../../client/" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../../../client/api" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sc-basic-authorship = { path = "../../../client/basic-authorship" , version = "0.8.0-alpha.2"} +sc-cli = { version = "0.8.0-alpha.5", path = "../../../client/cli" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sc-executor = { version = "0.8.0-alpha.5", path = "../../../client/executor" } +sc-service = { version = "0.8.0-alpha.5", path = "../../../client/service" } +sp-inherents = { version = "2.0.0-alpha.5", path = "../../../primitives/inherents" } +sc-transaction-pool = { version = "2.0.0-alpha.5", path = "../../../client/transaction-pool" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../../primitives/transaction-pool" } +sc-network = { version = "0.8.0-alpha.5", path = "../../../client/network" } +sc-consensus-aura = { version = "0.8.0-alpha.5", path = "../../../client/consensus/aura" } +sp-consensus-aura = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/aura" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } +sc-finality-grandpa = { version = "0.8.0-alpha.5", path = "../../../client/finality-grandpa" } +sp-finality-grandpa = { version = "2.0.0-alpha.5", path = "../../../primitives/finality-grandpa" } +sc-client = { version = "0.8.0-alpha.5", path = "../../../client/" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../../client/api" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sc-basic-authorship = { path = "../../../client/basic-authorship", version = "0.8.0-alpha.5"} -node-template-runtime = { version = "2.0.0-alpha.2", path = "../runtime" } +node-template-runtime = { version = "2.0.0-alpha.5", path = "../runtime" } [build-dependencies] vergen = "3.0.4" -build-script-utils = { version = "2.0.0-alpha.2", package = "substrate-build-script-utils", path = "../../../utils/build-script-utils" } +build-script-utils = { version = "2.0.0-alpha.5", package = "substrate-build-script-utils", path = "../../../utils/build-script-utils" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node-template/node/src/chain_spec.rs b/bin/node-template/node/src/chain_spec.rs index f1a7e29d44cb44e297bbb65591b8ed8396a7c37d..b57000fed7b642bd5d08503feb2e6dcd3b6f354d 100644 --- a/bin/node-template/node/src/chain_spec.rs +++ b/bin/node-template/node/src/chain_spec.rs @@ -4,7 +4,7 @@ use node_template_runtime::{ SudoConfig, SystemConfig, WASM_BINARY, Signature }; use sp_consensus_aura::sr25519::{AuthorityId as AuraId}; -use grandpa_primitives::{AuthorityId as GrandpaId}; +use sp_finality_grandpa::{AuthorityId as GrandpaId}; use sc_service; use sp_runtime::traits::{Verify, IdentifyAccount}; diff --git a/bin/node-template/node/src/service.rs b/bin/node-template/node/src/service.rs index 4b66f90fbb3732bbc07e59ced32a40717bd1b559..8269017072cb3359bd777a0ce7c70bb30ac15939 100644 --- a/bin/node-template/node/src/service.rs +++ b/bin/node-template/node/src/service.rs @@ -10,7 +10,7 @@ use sp_inherents::InherentDataProviders; use sc_executor::native_executor_instance; pub use sc_executor::NativeExecutor; use sp_consensus_aura::sr25519::{AuthorityPair as AuraPair}; -use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider, StorageAndProofProvider}; +use sc_finality_grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider, StorageAndProofProvider}; // Our native executor instance. native_executor_instance!( @@ -44,7 +44,7 @@ macro_rules! new_full_start { .ok_or_else(|| sc_service::Error::SelectChainRequired)?; let (grandpa_block_import, grandpa_link) = - grandpa::block_import(client.clone(), &(client.clone() as Arc<_>), select_chain)?; + sc_finality_grandpa::block_import(client.clone(), &(client.clone() as Arc<_>), select_chain)?; let aura_block_import = sc_consensus_aura::AuraBlockImport::<_, _, _, AuraPair>::new( grandpa_block_import.clone(), client.clone(), @@ -135,7 +135,7 @@ pub fn new_full(config: Configuration) None }; - let grandpa_config = grandpa::Config { + let grandpa_config = sc_finality_grandpa::Config { // FIXME #1578 make this available through chainspec gossip_duration: Duration::from_millis(333), justification_period: 512, @@ -153,13 +153,13 @@ pub fn new_full(config: Configuration) // and vote data availability than the observer. The observer has not // been tested extensively yet and having most nodes in a network run it // could lead to finality stalls. - let grandpa_config = grandpa::GrandpaParams { + let grandpa_config = sc_finality_grandpa::GrandpaParams { config: grandpa_config, link: grandpa_link, network: service.network(), inherent_data_providers: inherent_data_providers.clone(), telemetry_on_connect: Some(service.telemetry_on_connect_stream()), - voting_rule: grandpa::VotingRulesBuilder::default().build(), + voting_rule: sc_finality_grandpa::VotingRulesBuilder::default().build(), prometheus_registry: service.prometheus_registry() }; @@ -167,10 +167,10 @@ pub fn new_full(config: Configuration) // if it fails we take down the service with it. service.spawn_essential_task( "grandpa-voter", - grandpa::run_grandpa_voter(grandpa_config)? + sc_finality_grandpa::run_grandpa_voter(grandpa_config)? ); } else { - grandpa::setup_disabled_grandpa( + sc_finality_grandpa::setup_disabled_grandpa( service.client(), &inherent_data_providers, service.network(), @@ -204,7 +204,7 @@ pub fn new_light(config: Configuration) let fetch_checker = fetcher .map(|fetcher| fetcher.checker().clone()) .ok_or_else(|| "Trying to start light import queue without active fetch checker")?; - let grandpa_block_import = grandpa::light_block_import( + let grandpa_block_import = sc_finality_grandpa::light_block_import( client.clone(), backend, &(client.clone() as Arc<_>), diff --git a/bin/node-template/pallets/template/Cargo.toml b/bin/node-template/pallets/template/Cargo.toml index b39fcc1dae4d5bdb3b71edfaac02c55783879e29..8e53a9a53a65fd6b00c34ba280db719784741760 100644 --- a/bin/node-template/pallets/template/Cargo.toml +++ b/bin/node-template/pallets/template/Cargo.toml @@ -2,39 +2,38 @@ authors = ['Anonymous'] edition = '2018' name = 'pallet-template' -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" license = "Unlicense" homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet template" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } safe-mix = { default-features = false, version = '1.0.0' } [dependencies.frame-support] default-features = false -version = "2.0.0-alpha.2" +version = "2.0.0-alpha.5" path = "../../../../frame/support" -[dependencies.system] +[dependencies.frame-system] default-features = false -package = 'frame-system' -version = "2.0.0-alpha.2" +version = "2.0.0-alpha.5" path = "../../../../frame/system" [dev-dependencies.sp-core] default-features = false -version = "2.0.0-alpha.2" +version = "2.0.0-alpha.5" path = "../../../../primitives/core" [dev-dependencies.sp-io] default-features = false -version = "2.0.0-alpha.2" +version = "2.0.0-alpha.5" path = "../../../../primitives/io" [dev-dependencies.sp-runtime] default-features = false -version = "2.0.0-alpha.2" +version = "2.0.0-alpha.5" path = "../../../../primitives/runtime" @@ -44,5 +43,8 @@ std = [ 'codec/std', 'frame-support/std', 'safe-mix/std', - 'system/std' + 'frame-system/std' ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node-template/pallets/template/src/lib.rs b/bin/node-template/pallets/template/src/lib.rs index 892778adeb46b431cb46e87dd4e7ac4c1cea129b..a0daecfb72c9a14ffa7e7819400f0345c542b5b8 100644 --- a/bin/node-template/pallets/template/src/lib.rs +++ b/bin/node-template/pallets/template/src/lib.rs @@ -10,7 +10,7 @@ /// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs use frame_support::{decl_module, decl_storage, decl_event, decl_error, dispatch}; -use system::ensure_signed; +use frame_system::{self as system, ensure_signed}; #[cfg(test)] mod mock; @@ -75,6 +75,7 @@ decl_module! { /// Just a dummy entry point. /// function that can be called by the external world as an extrinsics call /// takes a parameter of the type `AccountId`, stores it, and emits an event + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn do_something(origin, something: u32) -> dispatch::DispatchResult { // Check it was signed and get the signer. See also: ensure_root and ensure_none let who = ensure_signed(origin)?; @@ -90,6 +91,7 @@ decl_module! { /// Another dummy entry point. /// takes no parameters, attempts to increment storage value, and possibly throws an error + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn cause_error(origin) -> dispatch::DispatchResult { // Check it was signed and get the signer. See also: ensure_root and ensure_none let _who = ensure_signed(origin)?; diff --git a/bin/node-template/pallets/template/src/mock.rs b/bin/node-template/pallets/template/src/mock.rs index 3179b56b22e46aa9d46ce990fc203d685c8a2d29..a93ac0359e3a2f2b8f4e0e558a0b254e33eb1c7e 100644 --- a/bin/node-template/pallets/template/src/mock.rs +++ b/bin/node-template/pallets/template/src/mock.rs @@ -6,6 +6,7 @@ use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, testing::Header, Perbill, }; +use frame_system as system; impl_outer_origin! { pub enum Origin for Test {} @@ -40,7 +41,7 @@ impl system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } impl Trait for Test { diff --git a/bin/node-template/runtime/Cargo.toml b/bin/node-template/runtime/Cargo.toml index 9268dd8c05035f4dd8022702e9792f421ab556c5..e8653e6df70e82e503a54e79c638e9a796e41913 100644 --- a/bin/node-template/runtime/Cargo.toml +++ b/bin/node-template/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-template-runtime" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Anonymous"] edition = "2018" license = "Unlicense" @@ -8,33 +8,33 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } -aura = { version = "2.0.0-alpha.2", default-features = false, package = "pallet-aura", path = "../../../frame/aura" } -balances = { version = "2.0.0-alpha.2", default-features = false, package = "pallet-balances", path = "../../../frame/balances" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/support" } -grandpa = { version = "2.0.0-alpha.2", default-features = false, package = "pallet-grandpa", path = "../../../frame/grandpa" } -randomness-collective-flip = { version = "2.0.0-alpha.2", default-features = false, package = "pallet-randomness-collective-flip", path = "../../../frame/randomness-collective-flip" } -sudo = { version = "2.0.0-alpha.2", default-features = false, package = "pallet-sudo", path = "../../../frame/sudo" } -system = { version = "2.0.0-alpha.2", default-features = false, package = "frame-system", path = "../../../frame/system" } -timestamp = { version = "2.0.0-alpha.2", default-features = false, package = "pallet-timestamp", path = "../../../frame/timestamp" } -transaction-payment = { version = "2.0.0-alpha.2", default-features = false, package = "pallet-transaction-payment", path = "../../../frame/transaction-payment" } -frame-executive = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/executive" } +aura = { version = "2.0.0-alpha.5", default-features = false, package = "pallet-aura", path = "../../../frame/aura" } +balances = { version = "2.0.0-alpha.5", default-features = false, package = "pallet-balances", path = "../../../frame/balances" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/support" } +grandpa = { version = "2.0.0-alpha.5", default-features = false, package = "pallet-grandpa", path = "../../../frame/grandpa" } +randomness-collective-flip = { version = "2.0.0-alpha.5", default-features = false, package = "pallet-randomness-collective-flip", path = "../../../frame/randomness-collective-flip" } +sudo = { version = "2.0.0-alpha.5", default-features = false, package = "pallet-sudo", path = "../../../frame/sudo" } +system = { version = "2.0.0-alpha.5", default-features = false, package = "frame-system", path = "../../../frame/system" } +timestamp = { version = "2.0.0-alpha.5", default-features = false, package = "pallet-timestamp", path = "../../../frame/timestamp" } +transaction-payment = { version = "2.0.0-alpha.5", default-features = false, package = "pallet-transaction-payment", path = "../../../frame/transaction-payment" } +frame-executive = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/executive" } serde = { version = "1.0.101", optional = true, features = ["derive"] } -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/api" } -sp-block-builder = { path = "../../../primitives/block-builder", default-features = false, version = "2.0.0-alpha.2"} -sp-consensus-aura = { version = "0.8.0-alpha.2", default-features = false, path = "../../../primitives/consensus/aura" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/core" } -sp-inherents = { path = "../../../primitives/inherents", default-features = false, version = "2.0.0-alpha.2"} -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/io" } -sp-offchain = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/offchain" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/runtime" } -sp-session = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/session" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/std" } -sp-transaction-pool = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/transaction-pool" } -sp-version = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/version" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/api" } +sp-block-builder = { path = "../../../primitives/block-builder", default-features = false, version = "2.0.0-alpha.5"} +sp-consensus-aura = { version = "0.8.0-alpha.5", default-features = false, path = "../../../primitives/consensus/aura" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/core" } +sp-inherents = { path = "../../../primitives/inherents", default-features = false, version = "2.0.0-alpha.5"} +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/io" } +sp-offchain = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/offchain" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/runtime" } +sp-session = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/session" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/std" } +sp-transaction-pool = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/transaction-pool" } +sp-version = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/version" } -template = { version = "2.0.0-alpha.2", default-features = false, path = "../pallets/template", package = "pallet-template" } +template = { version = "2.0.0-alpha.5", default-features = false, path = "../pallets/template", package = "pallet-template" } [build-dependencies] wasm-builder-runner = { version = "1.0.5", package = "substrate-wasm-builder-runner", path = "../../../utils/wasm-builder-runner" } @@ -68,3 +68,6 @@ std = [ "transaction-payment/std", "template/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 707799eab45194794e1942dc78995d0bad2595be..94f033fd8f58ee231f412e7a2cf6f7f53f77d49a 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -11,8 +11,8 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); use sp_std::prelude::*; use sp_core::OpaqueMetadata; use sp_runtime::{ - ApplyExtrinsicResult, transaction_validity::TransactionValidity, generic, create_runtime_str, - impl_opaque_keys, MultiSignature, + ApplyExtrinsicResult, generic, create_runtime_str, impl_opaque_keys, MultiSignature, + transaction_validity::{TransactionValidity, TransactionSource}, }; use sp_runtime::traits::{ BlakeTwo256, Block as BlockT, IdentityLookup, Verify, ConvertInto, IdentifyAccount @@ -162,7 +162,7 @@ impl system::Trait for Runtime { /// This type is being generated by `construct_runtime!`. type ModuleToIndex = ModuleToIndex; /// What to do if a new account is created. - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); /// What to do if an account is fully reaped from the system. type OnKilledAccount = (); /// The data to be stored in an account. @@ -318,8 +318,11 @@ impl_runtime_apis! { } impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction(tx: ::Extrinsic) -> TransactionValidity { - Executive::validate_transaction(tx) + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx) } } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index e18b6b228e65553543891d6e5cbcd6fb6094c752..804a74b8c4d696a5ad9fa058e03d5400940e7c38 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-cli" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] description = "Substrate node implementation in Rust." build = "build.rs" @@ -31,7 +31,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] # third-party dependencies -codec = { package = "parity-scale-codec", version = "1.2.0" } +codec = { package = "parity-scale-codec", version = "1.3.0" } serde = { version = "1.0.102", features = ["derive"] } futures = { version = "0.3.1", features = ["compat"] } hex-literal = "0.2.1" @@ -42,84 +42,90 @@ structopt = { version = "0.3.8", optional = true } tracing = "0.1.10" # primitives -sp-authority-discovery = { version = "2.0.0-alpha.2", path = "../../../primitives/authority-discovery" } -sp-consensus-babe = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/babe" } -grandpa-primitives = { version = "2.0.0-alpha.2", package = "sp-finality-grandpa", path = "../../../primitives/finality-grandpa" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/timestamp" } -sp-finality-tracker = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/finality-tracker" } -sp-inherents = { version = "2.0.0-alpha.2", path = "../../../primitives/inherents" } -sp-keyring = { version = "2.0.0-alpha.2", path = "../../../primitives/keyring" } -sp-io = { version = "2.0.0-alpha.2", path = "../../../primitives/io" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } +sp-authority-discovery = { version = "2.0.0-alpha.5", path = "../../../primitives/authority-discovery" } +sp-consensus-babe = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/babe" } +grandpa-primitives = { version = "2.0.0-alpha.5", package = "sp-finality-grandpa", path = "../../../primitives/finality-grandpa" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/timestamp" } +sp-finality-tracker = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/finality-tracker" } +sp-inherents = { version = "2.0.0-alpha.5", path = "../../../primitives/inherents" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../../../primitives/keyring" } +sp-io = { version = "2.0.0-alpha.5", path = "../../../primitives/io" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } # client dependencies -sc-client-api = { version = "2.0.0-alpha.2", path = "../../../client/api" } -sc-client = { version = "0.8.0-alpha.2", path = "../../../client/" } -sc-chain-spec = { version = "2.0.0-alpha.2", path = "../../../client/chain-spec" } -sc-transaction-pool = { version = "2.0.0-alpha.2", path = "../../../client/transaction-pool" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../../primitives/transaction-pool" } -sc-network = { version = "0.8.0-alpha.2", path = "../../../client/network" } -sc-consensus-babe = { version = "0.8.0-alpha.2", path = "../../../client/consensus/babe" } -grandpa = { version = "0.8.0-alpha.2", package = "sc-finality-grandpa", path = "../../../client/finality-grandpa" } -sc-client-db = { version = "0.8.0-alpha.2", default-features = false, path = "../../../client/db" } -sc-offchain = { version = "2.0.0-alpha.2", path = "../../../client/offchain" } -sc-rpc = { version = "2.0.0-alpha.2", path = "../../../client/rpc" } -sc-basic-authorship = { version = "0.8.0-alpha.2", path = "../../../client/basic-authorship" } -sc-service = { version = "0.8.0-alpha.2", default-features = false, path = "../../../client/service" } -sc-tracing = { version = "2.0.0-alpha.2", path = "../../../client/tracing" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "../../../client/telemetry" } -sc-authority-discovery = { version = "0.8.0-alpha.2", path = "../../../client/authority-discovery" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../../client/api" } +sc-client = { version = "0.8.0-alpha.5", path = "../../../client/" } +sc-chain-spec = { version = "2.0.0-alpha.5", path = "../../../client/chain-spec" } +sc-transaction-pool = { version = "2.0.0-alpha.5", path = "../../../client/transaction-pool" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../../primitives/transaction-pool" } +sc-network = { version = "0.8.0-alpha.5", path = "../../../client/network" } +sc-consensus-babe = { version = "0.8.0-alpha.5", path = "../../../client/consensus/babe" } +grandpa = { version = "0.8.0-alpha.5", package = "sc-finality-grandpa", path = "../../../client/finality-grandpa" } +sc-client-db = { version = "0.8.0-alpha.5", default-features = false, path = "../../../client/db" } +sc-offchain = { version = "2.0.0-alpha.5", path = "../../../client/offchain" } +sc-rpc = { version = "2.0.0-alpha.5", path = "../../../client/rpc" } +sc-basic-authorship = { version = "0.8.0-alpha.5", path = "../../../client/basic-authorship" } +sc-service = { version = "0.8.0-alpha.5", default-features = false, path = "../../../client/service" } +sc-tracing = { version = "2.0.0-alpha.5", path = "../../../client/tracing" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../../../client/telemetry" } +sc-authority-discovery = { version = "0.8.0-alpha.5", path = "../../../client/authority-discovery" } # frame dependencies -pallet-indices = { version = "2.0.0-alpha.2", path = "../../../frame/indices" } -pallet-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/timestamp" } -pallet-contracts = { version = "2.0.0-alpha.2", path = "../../../frame/contracts" } -frame-system = { version = "2.0.0-alpha.2", path = "../../../frame/system" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../../../frame/balances" } -pallet-transaction-payment = { version = "2.0.0-alpha.2", path = "../../../frame/transaction-payment" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/support" } -pallet-im-online = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/im-online" } -pallet-authority-discovery = { version = "2.0.0-alpha.2", path = "../../../frame/authority-discovery" } +pallet-indices = { version = "2.0.0-alpha.5", path = "../../../frame/indices" } +pallet-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/timestamp" } +pallet-contracts = { version = "2.0.0-alpha.5", path = "../../../frame/contracts" } +frame-system = { version = "2.0.0-alpha.5", path = "../../../frame/system" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../../../frame/balances" } +pallet-transaction-payment = { version = "2.0.0-alpha.5", path = "../../../frame/transaction-payment" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/support" } +pallet-im-online = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/im-online" } +pallet-authority-discovery = { version = "2.0.0-alpha.5", path = "../../../frame/authority-discovery" } +pallet-staking = { version = "2.0.0-alpha.5", path = "../../../frame/staking" } # node-specific dependencies -node-runtime = { version = "2.0.0-alpha.2", path = "../runtime" } -node-rpc = { version = "2.0.0-alpha.2", path = "../rpc" } -node-primitives = { version = "2.0.0-alpha.2", path = "../primitives" } -node-executor = { version = "2.0.0-alpha.2", path = "../executor" } +node-runtime = { version = "2.0.0-alpha.5", path = "../runtime" } +node-rpc = { version = "2.0.0-alpha.5", path = "../rpc" } +node-primitives = { version = "2.0.0-alpha.5", path = "../primitives" } +node-executor = { version = "2.0.0-alpha.5", path = "../executor" } # CLI-specific dependencies -sc-cli = { version = "0.8.0-alpha.2", optional = true, path = "../../../client/cli" } -frame-benchmarking-cli = { version = "2.0.0-alpha.2", optional = true, path = "../../../utils/frame/benchmarking-cli" } -node-transaction-factory = { version = "0.8.0-alpha.2", optional = true, path = "../transaction-factory" } -node-inspect = { version = "0.8.0-alpha.2", optional = true, path = "../inspect" } +sc-cli = { version = "0.8.0-alpha.5", optional = true, path = "../../../client/cli" } +frame-benchmarking-cli = { version = "2.0.0-alpha.5", optional = true, path = "../../../utils/frame/benchmarking-cli" } +node-transaction-factory = { version = "0.8.0-alpha.5", optional = true, path = "../transaction-factory" } +node-inspect = { version = "0.8.0-alpha.5", optional = true, path = "../inspect" } # WASM-specific dependencies wasm-bindgen = { version = "0.2.57", optional = true } wasm-bindgen-futures = { version = "0.4.7", optional = true } -browser-utils = { package = "substrate-browser-utils", path = "../../../utils/browser", optional = true, version = "0.8.0-alpha.2" } +browser-utils = { package = "substrate-browser-utils", path = "../../../utils/browser", optional = true, version = "0.8.0-alpha.5"} + +[target.'cfg(target_arch="x86_64")'.dependencies] +node-executor = { version = "2.0.0-alpha.4", path = "../executor", features = [ "wasmtime" ] } +sc-cli = { version = "0.8.0-alpha.4", optional = true, path = "../../../client/cli", features = [ "wasmtime" ] } +sc-service = { version = "0.8.0-alpha.4", default-features = false, path = "../../../client/service", features = [ "wasmtime" ] } [dev-dependencies] -sc-keystore = { version = "2.0.0-alpha.2", path = "../../../client/keystore" } -sc-consensus-babe = { version = "0.8.0-alpha.2", features = ["test-helpers"], path = "../../../client/consensus/babe" } -sc-consensus-epochs = { version = "0.8.0-alpha.2", path = "../../../client/consensus/epochs" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../../../client/keystore" } +sc-consensus-babe = { version = "0.8.0-alpha.5", features = ["test-helpers"], path = "../../../client/consensus/babe" } +sc-consensus-epochs = { version = "0.8.0-alpha.5", path = "../../../client/consensus/epochs" } sc-service-test = { version = "2.0.0-dev", path = "../../../client/service/test" } -futures = "0.3.1" +futures = "0.3.4" tempfile = "3.1.0" -assert_cmd = "0.12" +assert_cmd = "1.0" nix = "0.17" serde_json = "1.0" [build-dependencies] -build-script-utils = { version = "2.0.0-alpha.2", package = "substrate-build-script-utils", path = "../../../utils/build-script-utils" } +build-script-utils = { version = "2.0.0-alpha.5", package = "substrate-build-script-utils", path = "../../../utils/build-script-utils" } structopt = { version = "0.3.8", optional = true } -node-transaction-factory = { version = "0.8.0-alpha.2", optional = true, path = "../transaction-factory" } -node-inspect = { version = "0.8.0-alpha.2", optional = true, path = "../inspect" } -frame-benchmarking-cli = { version = "2.0.0-alpha.2", optional = true, path = "../../../utils/frame/benchmarking-cli" } +node-transaction-factory = { version = "0.8.0-alpha.5", optional = true, path = "../transaction-factory" } +node-inspect = { version = "0.8.0-alpha.5", optional = true, path = "../inspect" } +frame-benchmarking-cli = { version = "2.0.0-alpha.5", optional = true, path = "../../../utils/frame/benchmarking-cli" } [build-dependencies.sc-cli] -version = "0.8.0-alpha.2" +version = "0.8.0-alpha.5" package = "sc-cli" path = "../../../client/cli" optional = true @@ -129,7 +135,7 @@ version = "3.0.4" optional = true [features] -default = ["cli", "wasmtime"] +default = [ "cli" ] browser = [ "browser-utils", "wasm-bindgen", @@ -145,10 +151,7 @@ cli = [ "structopt", "vergen", ] -wasmtime = [ - "cli", - "node-executor/wasmtime", - "sc-cli/wasmtime", - "sc-service/wasmtime", -] runtime-benchmarks = [ "node-runtime/runtime-benchmarks" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/cli/res/flaming-fir.json b/bin/node/cli/res/flaming-fir.json index 573a9e3aa54a40e45b260629c63ec4666353aebe..7ed98239b54b62c2a3c6309d96b13dedcdb1e65d 100644 --- a/bin/node/cli/res/flaming-fir.json +++ b/bin/node/cli/res/flaming-fir.json @@ -23,7 +23,9 @@ "tokenSymbol": "FIR" }, "forkBlocks": null, - "badBlocks": null, + "badBlocks": [ + "0xf3b02820f81988282e1da41fd479ef2aa00d63d622863639ea15d48ab6533fdc" + ], "consensusEngine": null, "genesis": { "raw": { diff --git a/bin/node/cli/src/browser.rs b/bin/node/cli/src/browser.rs index e79ce273a32cc2ffde7ec1194fdbd81002e2fa25..d0746e8448bf24d96d50f76f7034797510e4038c 100644 --- a/bin/node/cli/src/browser.rs +++ b/bin/node/cli/src/browser.rs @@ -43,9 +43,9 @@ async fn start_inner(chain_spec: String, log_level: String) -> Result ChainSpec { "staging_testnet", staging_testnet_config_genesis, boot_nodes, - Some(TelemetryEndpoints::new(vec![(STAGING_TELEMETRY_URL.to_string(), 0)])), + Some(TelemetryEndpoints::new(vec![(STAGING_TELEMETRY_URL.to_string(), 0)]) + .expect("Staging telemetry url is valid; qed")), None, None, Default::default(), @@ -201,7 +203,14 @@ pub fn get_authority_keys_from_seed(seed: &str) -> ( /// Helper function to create GenesisConfig for testing pub fn testnet_genesis( - initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId)>, + initial_authorities: Vec<( + AccountId, + AccountId, + GrandpaId, + BabeId, + ImOnlineId, + AuthorityDiscoveryId, + )>, root_key: AccountId, endowed_accounts: Option>, enable_println: bool, @@ -243,7 +252,12 @@ pub fn testnet_genesis( }), pallet_session: Some(SessionConfig { keys: initial_authorities.iter().map(|x| { - (x.0.clone(), x.0.clone(), session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone())) + (x.0.clone(), x.0.clone(), session_keys( + x.2.clone(), + x.3.clone(), + x.4.clone(), + x.5.clone(), + )) }).collect::>(), }), pallet_staking: Some(StakingConfig { @@ -257,13 +271,14 @@ pub fn testnet_genesis( .. Default::default() }), pallet_democracy: Some(DemocracyConfig::default()), - pallet_collective_Instance1: Some(CouncilConfig { + pallet_elections_phragmen: Some(ElectionsConfig { members: endowed_accounts.iter() .take((num_endowed_accounts + 1) / 2) .cloned() + .map(|member| (member, STASH)) .collect(), - phantom: Default::default(), }), + pallet_collective_Instance1: Some(CouncilConfig::default()), pallet_collective_Instance2: Some(TechnicalCommitteeConfig { members: endowed_accounts.iter() .take((num_endowed_accounts + 1) / 2) diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index 21584f0898466dda3fca348bb40056b1436b43ac..6e9b5ca57526dc3e7305121805415085b0cb014f 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -25,6 +25,8 @@ where I: Iterator, T: Into + Clone, { + sc_cli::reset_signal_pipe_handler()?; + let args: Vec<_> = args.collect(); let opt = sc_cli::from_iter::(args.clone(), &version); diff --git a/bin/node/cli/src/factory_impl.rs b/bin/node/cli/src/factory_impl.rs index 1d1eabe29cbfb12ca43663be00b0969e852a30d9..cd7e3022e0b9a66e510707245118dbc123edee0d 100644 --- a/bin/node/cli/src/factory_impl.rs +++ b/bin/node/cli/src/factory_impl.rs @@ -58,6 +58,7 @@ impl FactoryState { frame_system::CheckWeight::new(), pallet_transaction_payment::ChargeTransactionPayment::from(0), Default::default(), + Default::default(), ) } } @@ -122,7 +123,7 @@ impl RuntimeAdapter for FactoryState { (*amount).into() ) ) - }, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), ())) + }, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), (), ())) } fn inherent_extrinsics(&self) -> InherentData { diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 6d9989b274ab2eb764624838602ed77e959aaa76..452b1fa3e62dbf6d917dce41f4d2bd6ecd89dd2f 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -397,6 +397,7 @@ mod tests { use sc_service::AbstractService; use crate::service::{new_full, new_light}; use sp_runtime::traits::IdentifyAccount; + use sp_transaction_pool::{MaintainedTransactionPool, ChainEvent}; type AccountPublic = ::Signer; @@ -414,7 +415,21 @@ mod tests { let dummy_runtime = ::tokio::runtime::Runtime::new().unwrap(); let block_factory = |service: &::FullService| { let block_id = BlockId::number(service.client().chain_info().best_number); - let parent_header = service.client().header(&block_id).unwrap().unwrap(); + let parent_header = service.client().best_header(&block_id) + .expect("db error") + .expect("best block should exist"); + + futures::executor::block_on( + service.transaction_pool().maintain( + ChainEvent::NewBlock { + is_new_best: true, + id: block_id.clone(), + retracted: vec![], + header: parent_header, + }, + ) + ); + let consensus_net = ConsensusNetwork::new(service.network(), service.client().clone()); let proposer_factory = consensus::ProposerFactory { client: service.client().clone(), @@ -464,6 +479,8 @@ mod tests { } #[test] + // It is "ignored", but the node-cli ignored tests are running on the CI. + // This can be run locally with `cargo test --release -p node-cli test_sync -- --ignored`. #[ignore] fn test_sync() { let keystore_path = tempfile::tempdir().expect("Creates keystore path"); @@ -504,6 +521,18 @@ mod tests { let parent_header = service.client().header(&parent_id).unwrap().unwrap(); let parent_hash = parent_header.hash(); let parent_number = *parent_header.number(); + + futures::executor::block_on( + service.transaction_pool().maintain( + ChainEvent::NewBlock { + is_new_best: true, + id: parent_id.clone(), + retracted: vec![], + header: parent_header.clone(), + }, + ) + ); + let mut proposer_factory = sc_basic_authorship::ProposerFactory::new( service.client(), service.transaction_pool() @@ -595,11 +624,12 @@ mod tests { check_weight, payment, Default::default(), + Default::default(), ); let raw_payload = SignedPayload::from_raw( function, extra, - (version, genesis_hash, genesis_hash, (), (), (), ()) + (version, genesis_hash, genesis_hash, (), (), (), (), ()) ); let signature = raw_payload.using_encoded(|payload| { signer.sign(payload) diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index a77efcf7f85260389800ca7f1f8018e0784f7324..2f1060a99884505ba94f1692f493bf8cdb01f43a 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-executor" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] description = "Substrate node implementation in Rust." edition = "2018" @@ -9,34 +9,34 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0" } -node-primitives = { version = "2.0.0-alpha.2", path = "../primitives" } -node-runtime = { version = "2.0.0-alpha.2", path = "../runtime" } -sc-executor = { version = "0.8.0-alpha.2", path = "../../../client/executor" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-io = { version = "2.0.0-alpha.2", path = "../../../primitives/io" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../../primitives/state-machine" } -sp-trie = { version = "2.0.0-alpha.2", path = "../../../primitives/trie" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +node-primitives = { version = "2.0.0-alpha.5", path = "../primitives" } +node-runtime = { version = "2.0.0-alpha.5", path = "../runtime" } +sc-executor = { version = "0.8.0-alpha.5", path = "../../../client/executor" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-io = { version = "2.0.0-alpha.5", path = "../../../primitives/io" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../../primitives/state-machine" } +sp-trie = { version = "2.0.0-alpha.5", path = "../../../primitives/trie" } trie-root = "0.16.0" -frame-benchmarking = { version = "2.0.0-alpha.2", path = "../../../frame/benchmarking" } +frame-benchmarking = { version = "2.0.0-alpha.5", path = "../../../frame/benchmarking" } [dev-dependencies] criterion = "0.3.0" -frame-support = { version = "2.0.0-alpha.2", path = "../../../frame/support" } -frame-system = { version = "2.0.0-alpha.2", path = "../../../frame/system" } -node-testing = { version = "2.0.0-alpha.2", path = "../testing" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../../../frame/balances" } -pallet-contracts = { version = "2.0.0-alpha.2", path = "../../../frame/contracts" } -pallet-grandpa = { version = "2.0.0-alpha.2", path = "../../../frame/grandpa" } -pallet-im-online = { version = "2.0.0-alpha.2", path = "../../../frame/im-online" } -pallet-indices = { version = "2.0.0-alpha.2", path = "../../../frame/indices" } -pallet-session = { version = "2.0.0-alpha.2", path = "../../../frame/session" } -pallet-timestamp = { version = "2.0.0-alpha.2", path = "../../../frame/timestamp" } -pallet-transaction-payment = { version = "2.0.0-alpha.2", path = "../../../frame/transaction-payment" } -pallet-treasury = { version = "2.0.0-alpha.2", path = "../../../frame/treasury" } -sp-application-crypto = { version = "2.0.0-alpha.2", path = "../../../primitives/application-crypto" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-externalities = { version = "0.8.0-alpha.3", path = "../../../primitives/externalities" } +frame-support = { version = "2.0.0-alpha.5", path = "../../../frame/support" } +frame-system = { version = "2.0.0-alpha.5", path = "../../../frame/system" } +node-testing = { version = "2.0.0-alpha.5", path = "../testing" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../../../frame/balances" } +pallet-contracts = { version = "2.0.0-alpha.5", path = "../../../frame/contracts" } +pallet-grandpa = { version = "2.0.0-alpha.5", path = "../../../frame/grandpa" } +pallet-im-online = { version = "2.0.0-alpha.5", path = "../../../frame/im-online" } +pallet-indices = { version = "2.0.0-alpha.5", path = "../../../frame/indices" } +pallet-session = { version = "2.0.0-alpha.5", path = "../../../frame/session" } +pallet-timestamp = { version = "2.0.0-alpha.5", path = "../../../frame/timestamp" } +pallet-transaction-payment = { version = "2.0.0-alpha.5", path = "../../../frame/transaction-payment" } +pallet-treasury = { version = "2.0.0-alpha.5", path = "../../../frame/treasury" } +sp-application-crypto = { version = "2.0.0-alpha.5", path = "../../../primitives/application-crypto" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-externalities = { version = "0.8.0-alpha.5", path = "../../../primitives/externalities" } substrate-test-client = { version = "2.0.0-dev", path = "../../../test-utils/client" } wabt = "0.9.2" @@ -52,3 +52,6 @@ stress-test = [] [[bench]] name = "bench" harness = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/executor/tests/submit_transaction.rs b/bin/node/executor/tests/submit_transaction.rs index 1a92aeca6ba77b61597f3f0d19317f67adca2b03..536cf486e38ae28e0df13fadd65ac9b5c20e99a0 100644 --- a/bin/node/executor/tests/submit_transaction.rs +++ b/bin/node/executor/tests/submit_transaction.rs @@ -15,7 +15,7 @@ // along with Substrate. If not, see . use node_runtime::{ - Call, Executive, Indices, Runtime, SubmitTransaction, UncheckedExtrinsic, + Call, Executive, Indices, Runtime, TransactionSubmitterOf, UncheckedExtrinsic, }; use sp_application_crypto::AppKey; use sp_core::testing::KeyStore; @@ -31,6 +31,8 @@ use codec::Decode; pub mod common; use self::common::*; +type SubmitTransaction = TransactionSubmitterOf; + #[test] fn should_submit_unsigned_transaction() { let mut t = new_test_ext(COMPACT_CODE, false); @@ -138,7 +140,7 @@ fn should_submit_signed_twice_from_the_same_account() { fn submitted_transaction_should_be_valid() { use codec::Encode; use frame_support::storage::StorageMap; - use sp_runtime::transaction_validity::ValidTransaction; + use sp_runtime::transaction_validity::{ValidTransaction, TransactionSource}; use sp_runtime::traits::StaticLookup; let mut t = new_test_ext(COMPACT_CODE, false); @@ -163,6 +165,7 @@ fn submitted_transaction_should_be_valid() { let tx0 = state.read().transactions[0].clone(); let mut t = new_test_ext(COMPACT_CODE, false); t.execute_with(|| { + let source = TransactionSource::External; let extrinsic = UncheckedExtrinsic::decode(&mut &*tx0).unwrap(); // add balance to the account let author = extrinsic.signature.clone().unwrap().0; @@ -172,15 +175,14 @@ fn submitted_transaction_should_be_valid() { >::insert(&address, account); // check validity - let res = Executive::validate_transaction(extrinsic); + let res = Executive::validate_transaction(source, extrinsic); assert_eq!(res.unwrap(), ValidTransaction { priority: 2_411_002_000_000, requires: vec![], provides: vec![(address, 0).encode()], - longevity: 127, + longevity: 128, propagate: true, }); }); } - diff --git a/bin/node/inspect/Cargo.toml b/bin/node/inspect/Cargo.toml index 022f4d0ca49f5252cedce67ea6750a4c5aae8a18..9e94fe74d68469d9570fe76292d004e9106ef049 100644 --- a/bin/node/inspect/Cargo.toml +++ b/bin/node/inspect/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-inspect" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -8,13 +8,16 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0" } +codec = { package = "parity-scale-codec", version = "1.3.0" } derive_more = "0.99" log = "0.4.8" -sc-cli = { version = "0.8.0-alpha.2", path = "../../../client/cli" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../../../client/api" } -sc-service = { version = "0.8.0-alpha.2", default-features = false, path = "../../../client/service" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } +sc-cli = { version = "0.8.0-alpha.5", path = "../../../client/cli" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../../client/api" } +sc-service = { version = "0.8.0-alpha.5", default-features = false, path = "../../../client/service" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } structopt = "0.3.8" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/primitives/Cargo.toml b/bin/node/primitives/Cargo.toml index cb271b987dba2e99c725258713d633863ff81e8b..81c5009f394d57332d5c9ef65ed8c6bb2cb6f120 100644 --- a/bin/node/primitives/Cargo.toml +++ b/bin/node/primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-primitives" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -8,11 +8,11 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/runtime" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/runtime" } [dev-dependencies] -sp-serializer = { version = "2.0.0-alpha.2", path = "../../../primitives/serializer" } +sp-serializer = { version = "2.0.0-alpha.5", path = "../../../primitives/serializer" } pretty_assertions = "0.6.1" [features] @@ -21,3 +21,6 @@ std = [ "sp-core/std", "sp-runtime/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/rpc-client/Cargo.toml b/bin/node/rpc-client/Cargo.toml index 8b37aff291341bd52cc11503fc4d37597769411c..df095bc5bb10d2574a01790f3ed32ea9c8585ebe 100644 --- a/bin/node/rpc-client/Cargo.toml +++ b/bin/node/rpc-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-rpc-client" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,7 +11,10 @@ repository = "https://github.com/paritytech/substrate/" env_logger = "0.7.0" futures = "0.1.29" hyper = "0.12.35" -jsonrpc-core-client = { version = "14.0.3", features = ["http", "ws"] } +jsonrpc-core-client = { version = "14.0.5", default-features = false, features = ["http"] } log = "0.4.8" -node-primitives = { version = "2.0.0-alpha.2", path = "../primitives" } -sc-rpc = { version = "2.0.0-alpha.2", path = "../../../client/rpc" } +node-primitives = { version = "2.0.0-alpha.5", path = "../primitives" } +sc-rpc = { version = "2.0.0-alpha.5", path = "../../../client/rpc" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index 1155eab304f8ae953207565d13f9434a4d27bb50..f1d230af90dc5e9e65da0ba4284355fa5292435a 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-rpc" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -8,20 +8,23 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sc-client = { version = "0.8.0-alpha.2", path = "../../../client/" } +sc-client = { version = "0.8.0-alpha.5", path = "../../../client/" } jsonrpc-core = "14.0.3" -node-primitives = { version = "2.0.0-alpha.2", path = "../primitives" } -node-runtime = { version = "2.0.0-alpha.2", path = "../runtime" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-api = { version = "2.0.0-alpha.2", path = "../../../primitives/api" } -pallet-contracts-rpc = { version = "0.8.0-alpha.2", path = "../../../frame/contracts/rpc/" } -pallet-transaction-payment-rpc = { version = "2.0.0-alpha.2", path = "../../../frame/transaction-payment/rpc/" } -substrate-frame-rpc-system = { version = "2.0.0-alpha.2", path = "../../../utils/frame/rpc/system" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../../primitives/transaction-pool" } -sc-consensus-babe = { version = "0.8.0-alpha.2", path = "../../../client/consensus/babe" } -sc-consensus-babe-rpc = { version = "0.8.0-alpha.2", path = "../../../client/consensus/babe/rpc" } -sp-consensus-babe = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/babe" } -sc-keystore = { version = "2.0.0-alpha.2", path = "../../../client/keystore" } -sc-consensus-epochs = { version = "0.8.0-alpha.2", path = "../../../client/consensus/epochs" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } +node-primitives = { version = "2.0.0-alpha.5", path = "../primitives" } +node-runtime = { version = "2.0.0-alpha.5", path = "../runtime" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../primitives/api" } +pallet-contracts-rpc = { version = "0.8.0-alpha.5", path = "../../../frame/contracts/rpc/" } +pallet-transaction-payment-rpc = { version = "2.0.0-alpha.5", path = "../../../frame/transaction-payment/rpc/" } +substrate-frame-rpc-system = { version = "2.0.0-alpha.5", path = "../../../utils/frame/rpc/system" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../../primitives/transaction-pool" } +sc-consensus-babe = { version = "0.8.0-alpha.5", path = "../../../client/consensus/babe" } +sc-consensus-babe-rpc = { version = "0.8.0-alpha.5", path = "../../../client/consensus/babe/rpc" } +sp-consensus-babe = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/babe" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../../../client/keystore" } +sc-consensus-epochs = { version = "0.8.0-alpha.5", path = "../../../client/consensus/epochs" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 15672715a415d0c6471e00faaadad3715b2c7789..07d3b8beda04fdd39183b5bb0c6b08f66703e5ad 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-runtime" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" build = "build.rs" @@ -11,70 +11,70 @@ repository = "https://github.com/paritytech/substrate/" [dependencies] # third-party dependencies -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } integer-sqrt = { version = "0.1.2" } -rustc-hex = { version = "2.0", optional = true } serde = { version = "1.0.102", optional = true } # primitives -sp-authority-discovery = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/authority-discovery" } -sp-consensus-babe = { version = "0.8.0-alpha.2", default-features = false, path = "../../../primitives/consensus/babe" } -sp-block-builder = { path = "../../../primitives/block-builder", default-features = false, version = "2.0.0-alpha.2"} -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/inherents" } -node-primitives = { version = "2.0.0-alpha.2", default-features = false, path = "../primitives" } -sp-offchain = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/offchain" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/std" } -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/api" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/runtime" } -sp-staking = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/staking" } -sp-keyring = { version = "2.0.0-alpha.2", optional = true, path = "../../../primitives/keyring" } -sp-session = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/session" } -sp-transaction-pool = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/transaction-pool" } -sp-version = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/version" } +sp-authority-discovery = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/authority-discovery" } +sp-consensus-babe = { version = "0.8.0-alpha.5", default-features = false, path = "../../../primitives/consensus/babe" } +sp-block-builder = { path = "../../../primitives/block-builder", default-features = false, version = "2.0.0-alpha.5"} +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/inherents" } +node-primitives = { version = "2.0.0-alpha.5", default-features = false, path = "../primitives" } +sp-offchain = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/offchain" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/std" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/api" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/runtime" } +sp-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/staking" } +sp-keyring = { version = "2.0.0-alpha.5", optional = true, path = "../../../primitives/keyring" } +sp-session = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/session" } +sp-transaction-pool = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/transaction-pool" } +sp-version = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/version" } # frame dependencies -frame-executive = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/executive" } -frame-benchmarking = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/benchmarking", optional = true } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/system" } -frame-system-rpc-runtime-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/system/rpc/runtime-api/" } -pallet-authority-discovery = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/authority-discovery" } -pallet-authorship = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/authorship" } -pallet-babe = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/babe" } -pallet-balances = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/balances" } -pallet-collective = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/collective" } -pallet-contracts = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/contracts" } -pallet-contracts-primitives = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/contracts/common/" } -pallet-contracts-rpc-runtime-api = { version = "0.8.0-alpha.2", default-features = false, path = "../../../frame/contracts/rpc/runtime-api/" } -pallet-democracy = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/democracy" } -pallet-elections-phragmen = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/elections-phragmen" } -pallet-finality-tracker = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/finality-tracker" } -pallet-grandpa = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/grandpa" } -pallet-im-online = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/im-online" } -pallet-indices = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/indices" } -pallet-identity = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/identity" } -pallet-membership = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/membership" } -pallet-offences = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/offences" } -pallet-randomness-collective-flip = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/randomness-collective-flip" } -pallet-recovery = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/recovery" } -pallet-session = { version = "2.0.0-alpha.2", features = ["historical"], path = "../../../frame/session", default-features = false } -pallet-staking = { version = "2.0.0-alpha.2", features = ["migrate"], path = "../../../frame/staking", default-features = false } -pallet-staking-reward-curve = { version = "2.0.0-alpha.2", path = "../../../frame/staking/reward-curve" } -pallet-sudo = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/sudo" } -pallet-society = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/society" } -pallet-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/timestamp" } -pallet-treasury = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/treasury" } -pallet-utility = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/utility" } -pallet-transaction-payment = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/transaction-payment" } -pallet-transaction-payment-rpc-runtime-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/transaction-payment/rpc/runtime-api/" } -pallet-vesting = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/vesting" } +frame-executive = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/executive" } +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/benchmarking", optional = true } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/system" } +frame-system-rpc-runtime-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/system/rpc/runtime-api/" } +pallet-authority-discovery = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/authority-discovery" } +pallet-authorship = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/authorship" } +pallet-babe = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/babe" } +pallet-balances = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/balances" } +pallet-collective = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/collective" } +pallet-contracts = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/contracts" } +pallet-contracts-primitives = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/contracts/common/" } +pallet-contracts-rpc-runtime-api = { version = "0.8.0-alpha.5", default-features = false, path = "../../../frame/contracts/rpc/runtime-api/" } +pallet-democracy = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/democracy" } +pallet-elections-phragmen = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/elections-phragmen" } +pallet-finality-tracker = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/finality-tracker" } +pallet-grandpa = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/grandpa" } +pallet-im-online = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/im-online" } +pallet-indices = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/indices" } +pallet-identity = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/identity" } +pallet-membership = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/membership" } +pallet-offences = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/offences" } +pallet-randomness-collective-flip = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/randomness-collective-flip" } +pallet-recovery = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/recovery" } +pallet-session = { version = "2.0.0-alpha.5", features = ["historical"], path = "../../../frame/session", default-features = false } +pallet-session-benchmarking = { version = "2.0.0-alpha.5", path = "../../../frame/session/benchmarking", default-features = false, optional = true } +pallet-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/staking" } +pallet-staking-reward-curve = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/staking/reward-curve" } +pallet-sudo = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/sudo" } +pallet-society = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/society" } +pallet-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/timestamp" } +pallet-treasury = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/treasury" } +pallet-utility = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/utility" } +pallet-transaction-payment = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/transaction-payment" } +pallet-transaction-payment-rpc-runtime-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/transaction-payment/rpc/runtime-api/" } +pallet-vesting = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/vesting" } [build-dependencies] wasm-builder-runner = { version = "1.0.5", package = "substrate-wasm-builder-runner", path = "../../../utils/wasm-builder-runner" } [dev-dependencies] -sp-io = { version = "2.0.0-alpha.2", path = "../../../primitives/io" } +sp-io = { version = "2.0.0-alpha.5", path = "../../../primitives/io" } [features] default = ["std"] @@ -107,7 +107,6 @@ std = [ "sp-core/std", "pallet-randomness-collective-flip/std", "sp-std/std", - "rustc-hex", "serde", "pallet-session/std", "sp-api/std", @@ -134,8 +133,24 @@ std = [ ] runtime-benchmarks = [ "frame-benchmarking", - "pallet-timestamp/runtime-benchmarks", - "pallet-identity/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-collective/runtime-benchmarks", + "pallet-democracy/runtime-benchmarks", + "pallet-elections-phragmen/runtime-benchmarks", + "pallet-identity/runtime-benchmarks", + "pallet-im-online/runtime-benchmarks", + "pallet-society/runtime-benchmarks", + "pallet-staking/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-treasury/runtime-benchmarks", + "pallet-utility/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", + "pallet-collective/runtime-benchmarks", + "pallet-session-benchmarking", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index f15dbcbf5e48d87b36df8c5cb85130e3731c5a6c..881807ea24dc5c7d3f9890b01de092a58a281703 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -35,7 +35,7 @@ use sp_runtime::{ impl_opaque_keys, generic, create_runtime_str, }; use sp_runtime::curve::PiecewiseLinear; -use sp_runtime::transaction_validity::TransactionValidity; +use sp_runtime::transaction_validity::{TransactionValidity, TransactionSource}; use sp_runtime::traits::{ self, BlakeTwo256, Block as BlockT, StaticLookup, SaturatedConversion, ConvertInto, OpaqueKeys, @@ -59,7 +59,7 @@ pub use pallet_timestamp::Call as TimestampCall; pub use pallet_balances::Call as BalancesCall; pub use pallet_contracts::Gas; pub use frame_support::StorageValue; -pub use pallet_staking::StakerStatus; +pub use pallet_staking::{StakerStatus, LockStakingStatus}; /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; @@ -73,6 +73,52 @@ use constants::{time::*, currency::*}; #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +/// A transaction submitter with the given key type. +pub type TransactionSubmitterOf = TransactionSubmitter; + +/// Submits transaction with the node's public and signature type. Adheres to the signed extension +/// format of the chain. +impl frame_system::offchain::CreateTransaction for Runtime { + type Public = ::Signer; + type Signature = Signature; + + fn create_transaction>( + call: Call, + public: Self::Public, + account: AccountId, + index: Index, + ) -> Option<(Call, ::SignaturePayload)> { + // take the biggest period possible. + let period = BlockHashCount::get() + .checked_next_power_of_two() + .map(|c| c / 2) + .unwrap_or(2) as u64; + let current_block = System::block_number() + .saturated_into::() + // The `System::block_number` is initialized with `n+1`, + // so the actual block number is `n`. + .saturating_sub(1); + let tip = 0; + let extra: SignedExtra = ( + frame_system::CheckVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(generic::Era::mortal(period, current_block)), + frame_system::CheckNonce::::from(index), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + Default::default(), + Default::default(), + ); + let raw_payload = SignedPayload::new(call, extra).map_err(|e| { + debug::warn!("Unable to create signed payload: {:?}", e); + }).ok()?; + let signature = TSigner::sign(public, &raw_payload)?; + let address = Indices::unlookup(account); + let (call, extra, _) = raw_payload.deconstruct(); + Some((call, (address, signature, extra))) + } +} + /// Runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node"), @@ -82,8 +128,8 @@ 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: 237, - impl_version: 0, + spec_version: 240, + impl_version: 1, apis: RUNTIME_API_VERSIONS, }; @@ -140,7 +186,6 @@ impl frame_system::Trait for Runtime { type Version = Version; type ModuleToIndex = ModuleToIndex; type AccountData = pallet_balances::AccountData; - type MigrateAccount = (Balances, Identity, Democracy, Elections, ImOnline, Recovery, Session, Society, Staking, Vesting); type OnNewAccount = (); type OnKilledAccount = (); } @@ -256,6 +301,7 @@ impl pallet_session::Trait for Runtime { type SessionHandler = ::KeyTypeIdProviders; type Keys = SessionKeys; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = Babe; } impl pallet_session::historical::Trait for Runtime { @@ -279,12 +325,13 @@ parameter_types! { pub const BondingDuration: pallet_staking::EraIndex = 24 * 28; pub const SlashDeferDuration: pallet_staking::EraIndex = 24 * 7; // 1/4 the bonding duration. pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; + pub const ElectionLookahead: BlockNumber = 25; // 10 minutes per session => 100 block. pub const MaxNominatorRewardedPerValidator: u32 = 64; } impl pallet_staking::Trait for Runtime { type Currency = Balances; - type Time = Timestamp; + type UnixTime = Timestamp; type CurrencyToVote = CurrencyToVoteHandler; type RewardRemainder = Treasury; type Event = Event; @@ -297,13 +344,18 @@ impl pallet_staking::Trait for Runtime { type SlashCancelOrigin = pallet_collective::EnsureProportionAtLeast<_3, _4, AccountId, CouncilCollective>; type SessionInterface = Self; type RewardCurve = RewardCurve; + type NextNewSession = Session; + type ElectionLookahead = ElectionLookahead; + type Call = Call; + type SubmitTransaction = TransactionSubmitterOf<()>; type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; } parameter_types! { pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; pub const VotingPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; - pub const EmergencyVotingPeriod: BlockNumber = 3 * 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; @@ -329,7 +381,9 @@ impl pallet_democracy::Trait for Runtime { /// 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>; - type EmergencyVotingPeriod = EmergencyVotingPeriod; + type InstantOrigin = pallet_collective::EnsureProportionAtLeast<_1, _1, AccountId, TechnicalCollective>; + type InstantAllowed = InstantAllowed; + 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>; // Any single technical committee member may veto a coming council proposal, however they can @@ -364,6 +418,9 @@ impl pallet_elections_phragmen::Trait for Runtime { type Event = Event; type Currency = Balances; type ChangeMembers = Council; + // NOTE: this implies that council's genesis members cannot be set directly and must come from + // this module. + type InitializeMembers = Council; type CurrencyToVote = CurrencyToVoteHandler; type CandidacyBond = CandidacyBond; type VotingBond = VotingBond; @@ -468,9 +525,6 @@ impl pallet_sudo::Trait for Runtime { type Call = Call; } -/// A runtime transaction submitter. -pub type SubmitTransaction = TransactionSubmitter; - parameter_types! { pub const SessionDuration: BlockNumber = EPOCH_DURATION_IN_SLOTS as _; } @@ -479,7 +533,7 @@ impl pallet_im_online::Trait for Runtime { type AuthorityId = ImOnlineId; type Event = Event; type Call = Call; - type SubmitTransaction = SubmitTransaction; + type SubmitTransaction = TransactionSubmitterOf; type SessionDuration = SessionDuration; type ReportUnresponsiveness = Offences; } @@ -528,46 +582,6 @@ impl pallet_identity::Trait for Runtime { type RegistrarOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>; } -impl frame_system::offchain::CreateTransaction for Runtime { - type Public = ::Signer; - type Signature = Signature; - - fn create_transaction>( - call: Call, - public: Self::Public, - account: AccountId, - index: Index, - ) -> Option<(Call, ::SignaturePayload)> { - // take the biggest period possible. - let period = BlockHashCount::get() - .checked_next_power_of_two() - .map(|c| c / 2) - .unwrap_or(2) as u64; - let current_block = System::block_number() - .saturated_into::() - // The `System::block_number` is initialized with `n+1`, - // so the actual block number is `n`. - .saturating_sub(1); - let tip = 0; - let extra: SignedExtra = ( - frame_system::CheckVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(generic::Era::mortal(period, current_block)), - frame_system::CheckNonce::::from(index), - frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(tip), - Default::default(), - ); - let raw_payload = SignedPayload::new(call, extra).map_err(|e| { - debug::warn!("Unable to create signed payload: {:?}", e); - }).ok()?; - let signature = TSigner::sign(public, &raw_payload)?; - let address = Indices::unlookup(account); - let (call, extra, _) = raw_payload.deconstruct(); - Some((call, (address, signature, extra))) - } -} - parameter_types! { pub const ConfigDepositBase: Balance = 5 * DOLLARS; pub const FriendDepositFactor: Balance = 50 * CENTS; @@ -636,12 +650,12 @@ construct_runtime!( Indices: pallet_indices::{Module, Call, Storage, Config, Event}, Balances: pallet_balances::{Module, Call, Storage, Config, Event}, TransactionPayment: pallet_transaction_payment::{Module, Storage}, - Staking: pallet_staking::{Module, Call, Config, Storage, Event}, + Staking: pallet_staking::{Module, Call, Config, Storage, Event, ValidateUnsigned}, Session: pallet_session::{Module, Call, Storage, Event, Config}, Democracy: pallet_democracy::{Module, Call, Storage, Config, Event}, Council: pallet_collective::::{Module, Call, Storage, Origin, Event, Config}, TechnicalCommittee: pallet_collective::::{Module, Call, Storage, Origin, Event, Config}, - Elections: pallet_elections_phragmen::{Module, Call, Storage, Event}, + Elections: pallet_elections_phragmen::{Module, Call, Storage, Event, Config}, TechnicalMembership: pallet_membership::::{Module, Call, Storage, Event, Config}, FinalityTracker: pallet_finality_tracker::{Module, Call, Inherent}, Grandpa: pallet_grandpa::{Module, Call, Storage, Config, Event}, @@ -678,6 +692,7 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, pallet_contracts::CheckBlockGasLimit, + pallet_staking::LockStakingStatus, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; @@ -732,8 +747,11 @@ impl_runtime_apis! { } impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction(tx: ::Extrinsic) -> TransactionValidity { - Executive::validate_transaction(tx) + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx) } } @@ -843,48 +861,35 @@ impl_runtime_apis! { #[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark for Runtime { fn dispatch_benchmark( - module: Vec, - extrinsic: Vec, + pallet: Vec, + benchmark: Vec, lowest_range_values: Vec, highest_range_values: Vec, steps: Vec, repeat: u32, - ) -> Result, sp_runtime::RuntimeString> { - use frame_benchmarking::Benchmarking; - - let result = match module.as_slice() { - b"pallet-balances" | b"balances" => Balances::run_benchmark( - extrinsic, - lowest_range_values, - highest_range_values, - steps, - repeat, - ), - b"pallet-identity" | b"identity" => Identity::run_benchmark( - extrinsic, - lowest_range_values, - highest_range_values, - steps, - repeat, - ), - b"pallet-timestamp" | b"timestamp" => Timestamp::run_benchmark( - extrinsic, - lowest_range_values, - highest_range_values, - steps, - repeat, - ), - b"pallet-vesting" | b"vesting" => Vesting::run_benchmark( - extrinsic, - lowest_range_values, - highest_range_values, - steps, - repeat, - ), - _ => Err("Benchmark not found for this pallet."), - }; - - result.map_err(|e| e.into()) + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark}; + // 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::Module as SessionBench; + impl pallet_session_benchmarking::Trait for Runtime {} + + let mut batches = Vec::::new(); + let params = (&pallet, &benchmark, &lowest_range_values, &highest_range_values, &steps, repeat); + add_benchmark!(params, batches, b"balances", Balances); + add_benchmark!(params, batches, b"collective", Council); + add_benchmark!(params, batches, b"democracy", Democracy); + add_benchmark!(params, batches, b"identity", Identity); + add_benchmark!(params, batches, b"im-online", ImOnline); + add_benchmark!(params, batches, b"session", SessionBench::); + add_benchmark!(params, batches, b"staking", Staking); + add_benchmark!(params, batches, b"timestamp", Timestamp); + add_benchmark!(params, batches, b"treasury", Treasury); + add_benchmark!(params, batches, b"utility", Utility); + add_benchmark!(params, batches, b"vesting", Vesting); + if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } + Ok(batches) } } } @@ -913,39 +918,7 @@ mod tests { >, {} - is_submit_signed_transaction::(); - is_sign_and_submit_transaction::(); - } - - #[test] - fn block_hooks_weight_should_not_exceed_limits() { - use frame_support::weights::WeighBlock; - let check_for_block = |b| { - let block_hooks_weight = - >::on_initialize(b) + - >::on_finalize(b); - - assert_eq!( - block_hooks_weight, - 0, - "This test might fail simply because the value being compared to has increased to a \ - module declaring a new weight for a hook or call. In this case update the test and \ - happily move on.", - ); - - // Invariant. Always must be like this to have a sane chain. - assert!(block_hooks_weight < MaximumBlockWeight::get()); - - // Warning. - if block_hooks_weight > MaximumBlockWeight::get() / 2 { - println!( - "block hooks weight is consuming more than a block's capacity. You probably want \ - to re-think this. This test will fail now." - ); - assert!(false); - } - }; - - let _ = (0..100_000).for_each(check_for_block); + is_submit_signed_transaction::>(); + is_sign_and_submit_transaction::>(); } } diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index 97d76467fea4289e4bdea0b6ed7ce8d70d088019..b16e3135741c9d264cb8d1116d53ee7baea2f0fa 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-testing" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] description = "Test utilities for Substrate node." edition = "2018" @@ -10,49 +10,52 @@ repository = "https://github.com/paritytech/substrate/" publish = true [dependencies] -pallet-balances = { version = "2.0.0-alpha.2", path = "../../../frame/balances" } -sc-client = { version = "0.8.0-alpha.2", path = "../../../client/" } -sc-client-db = { version = "0.8.0-alpha.2", path = "../../../client/db/", features = ["kvdb-rocksdb"] } -sc-client-api = { version = "2.0.0-alpha.2", path = "../../../client/api/" } -codec = { package = "parity-scale-codec", version = "1.2.0" } -pallet-contracts = { version = "2.0.0-alpha.2", path = "../../../frame/contracts" } -pallet-grandpa = { version = "2.0.0-alpha.2", path = "../../../frame/grandpa" } -pallet-indices = { version = "2.0.0-alpha.2", path = "../../../frame/indices" } -sp-keyring = { version = "2.0.0-alpha.2", path = "../../../primitives/keyring" } -node-executor = { version = "2.0.0-alpha.2", path = "../executor" } -node-primitives = { version = "2.0.0-alpha.2", path = "../primitives" } -node-runtime = { version = "2.0.0-alpha.2", path = "../runtime" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-io = { version = "2.0.0-alpha.2", path = "../../../primitives/io" } -frame-support = { version = "2.0.0-alpha.2", path = "../../../frame/support" } -pallet-session = { version = "2.0.0-alpha.2", path = "../../../frame/session" } -pallet-society = { version = "2.0.0-alpha.2", path = "../../../frame/society" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -pallet-staking = { version = "2.0.0-alpha.2", path = "../../../frame/staking" } -sc-executor = { version = "0.8.0-alpha.2", path = "../../../client/executor", features = ["wasmtime"] } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } -frame-system = { version = "2.0.0-alpha.2", path = "../../../frame/system" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../../../frame/balances" } +sc-client = { version = "0.8.0-alpha.5", path = "../../../client/" } +sc-client-db = { version = "0.8.0-alpha.5", path = "../../../client/db/", features = ["kvdb-rocksdb"] } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../../client/api/" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +pallet-contracts = { version = "2.0.0-alpha.5", path = "../../../frame/contracts" } +pallet-grandpa = { version = "2.0.0-alpha.5", path = "../../../frame/grandpa" } +pallet-indices = { version = "2.0.0-alpha.5", path = "../../../frame/indices" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../../../primitives/keyring" } +node-executor = { version = "2.0.0-alpha.5", path = "../executor" } +node-primitives = { version = "2.0.0-alpha.5", path = "../primitives" } +node-runtime = { version = "2.0.0-alpha.5", path = "../runtime" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-io = { version = "2.0.0-alpha.5", path = "../../../primitives/io" } +frame-support = { version = "2.0.0-alpha.5", path = "../../../frame/support" } +pallet-session = { version = "2.0.0-alpha.5", path = "../../../frame/session" } +pallet-society = { version = "2.0.0-alpha.5", path = "../../../frame/society" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +pallet-staking = { version = "2.0.0-alpha.5", path = "../../../frame/staking" } +sc-executor = { version = "0.8.0-alpha.5", path = "../../../client/executor", features = ["wasmtime"] } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } +frame-system = { version = "2.0.0-alpha.5", path = "../../../frame/system" } substrate-test-client = { version = "2.0.0-dev", path = "../../../test-utils/client" } -pallet-timestamp = { version = "2.0.0-alpha.2", path = "../../../frame/timestamp" } -pallet-transaction-payment = { version = "2.0.0-alpha.2", path = "../../../frame/transaction-payment" } -pallet-treasury = { version = "2.0.0-alpha.2", path = "../../../frame/treasury" } +pallet-timestamp = { version = "2.0.0-alpha.5", path = "../../../frame/timestamp" } +pallet-transaction-payment = { version = "2.0.0-alpha.5", path = "../../../frame/transaction-payment" } +pallet-treasury = { version = "2.0.0-alpha.5", path = "../../../frame/treasury" } wabt = "0.9.2" -sp-api = { version = "2.0.0-alpha.2", path = "../../../primitives/api" } -sp-finality-tracker = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/finality-tracker" } -sp-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/timestamp" } -sp-block-builder = { version = "2.0.0-alpha.2", path = "../../../primitives/block-builder" } -sc-block-builder = { version = "0.8.0-alpha.3", path = "../../../client/block-builder" } -sp-inherents = { version = "2.0.0-alpha.2", path = "../../../primitives/inherents" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../primitives/api" } +sp-finality-tracker = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/finality-tracker" } +sp-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/timestamp" } +sp-block-builder = { version = "2.0.0-alpha.5", path = "../../../primitives/block-builder" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "../../../client/block-builder" } +sp-inherents = { version = "2.0.0-alpha.5", path = "../../../primitives/inherents" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } log = "0.4.8" tempfile = "3.1.0" fs_extra = "1" [dev-dependencies] criterion = "0.3.0" -sc-cli = { version = "0.8.0-alpha.2", path = "../../../client/cli" } -sc-service = { version = "0.8.0-alpha.2", path = "../../../client/service", features = ["rocksdb"] } +sc-cli = { version = "0.8.0-alpha.5", path = "../../../client/cli" } +sc-service = { version = "0.8.0-alpha.5", path = "../../../client/service", features = ["rocksdb"] } [[bench]] name = "import" harness = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index e35059e0c6ffd300cb2941e4322e22c8bbf832ee..8a57010770f3dedf9795e0da462455943fb6f74d 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -109,6 +109,7 @@ pub fn config_endowed( pallet_collective_Instance1: Some(Default::default()), pallet_collective_Instance2: Some(Default::default()), pallet_membership_Instance1: Some(Default::default()), + pallet_elections_phragmen: Some(Default::default()), pallet_sudo: Some(Default::default()), pallet_treasury: Some(Default::default()), pallet_society: Some(SocietyConfig { diff --git a/bin/node/testing/src/keyring.rs b/bin/node/testing/src/keyring.rs index 6b0d06875d6924a0715f74334390193736009bd0..5fa1e48b0321813d3c9bec039ec3212c5be4216a 100644 --- a/bin/node/testing/src/keyring.rs +++ b/bin/node/testing/src/keyring.rs @@ -75,6 +75,7 @@ pub fn signed_extra(nonce: Index, extra_fee: Balance) -> SignedExtra { frame_system::CheckWeight::new(), pallet_transaction_payment::ChargeTransactionPayment::from(extra_fee), Default::default(), + Default::default(), ) } diff --git a/bin/node/transaction-factory/Cargo.toml b/bin/node/transaction-factory/Cargo.toml index 44f2d87f9c56dd82cf9abdae08b287546a181321..33ebeb767ae37e75c04309c958e3de79096bee67 100644 --- a/bin/node/transaction-factory/Cargo.toml +++ b/bin/node/transaction-factory/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-transaction-factory" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -8,16 +8,19 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-block-builder = { version = "2.0.0-alpha.2", path = "../../../primitives/block-builder" } -sc-cli = { version = "0.8.0-alpha.2", path = "../../../client/cli" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../../../client/api" } -sc-block-builder = { version = "0.8.0-alpha.2", path = "../../../client/block-builder" } -sc-client = { version = "0.8.0-alpha.2", path = "../../../client" } -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } +sp-block-builder = { version = "2.0.0-alpha.5", path = "../../../primitives/block-builder" } +sc-cli = { version = "0.8.0-alpha.5", path = "../../../client/cli" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../../client/api" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "../../../client/block-builder" } +sc-client = { version = "0.8.0-alpha.5", path = "../../../client" } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } log = "0.4.8" -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-api = { version = "2.0.0-alpha.2", path = "../../../primitives/api" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sc-service = { version = "0.8.0-alpha.2", default-features = false, path = "../../../client/service" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../primitives/api" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sc-service = { version = "0.8.0-alpha.5", default-features = false, path = "../../../client/service" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/utils/chain-spec-builder/Cargo.toml b/bin/utils/chain-spec-builder/Cargo.toml index 5e51c4358e513d8c6a3ec5164d3aba0520d3f405..9b03dd17b76de499094287fc126a0607c866a3a3 100644 --- a/bin/utils/chain-spec-builder/Cargo.toml +++ b/bin/utils/chain-spec-builder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chain-spec-builder" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" build = "build.rs" @@ -10,8 +10,11 @@ repository = "https://github.com/paritytech/substrate/" [dependencies] ansi_term = "0.12.1" -sc-keystore = { version = "2.0.0-alpha.2", path = "../../../client/keystore" } -node-cli = { version = "2.0.0-alpha.2", path = "../../node/cli" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../../../client/keystore" } +node-cli = { version = "2.0.0-alpha.5", path = "../../node/cli" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } rand = "0.7.2" structopt = "0.3.8" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/utils/subkey/Cargo.toml b/bin/utils/subkey/Cargo.toml index 252978a3a98cbc143139b9cdfba1169a1ebc6de9..9bf20146a990d8dd876495caa867812d331949a5 100644 --- a/bin/utils/subkey/Cargo.toml +++ b/bin/utils/subkey/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subkey" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,25 +9,24 @@ repository = "https://github.com/paritytech/substrate/" [dependencies] futures = "0.1.29" -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -node-runtime = { version = "2.0.0-alpha.2", path = "../../node/runtime" } -node-primitives = { version = "2.0.0-alpha.2", path = "../../node/primitives" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +node-runtime = { version = "2.0.0-alpha.5", path = "../../node/runtime" } +node-primitives = { version = "2.0.0-alpha.5", path = "../../node/primitives" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } rand = "0.7.2" clap = "2.33.0" tiny-bip39 = "0.7" -rustc-hex = "2.0.1" -substrate-bip39 = "0.3.1" +substrate-bip39 = "0.4.1" hex = "0.4.0" hex-literal = "0.2.1" -codec = { package = "parity-scale-codec", version = "1.2.0" } -frame-system = { version = "2.0.0-alpha.2", path = "../../../frame/system" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../../../frame/balances" } -pallet-transaction-payment = { version = "2.0.0-alpha.2", path = "../../../frame/transaction-payment" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +frame-system = { version = "2.0.0-alpha.5", path = "../../../frame/system" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../../../frame/balances" } +pallet-transaction-payment = { version = "2.0.0-alpha.5", path = "../../../frame/transaction-payment" } rpassword = "4.0.1" itertools = "0.8.2" derive_more = { version = "0.99.2" } -sc-rpc = { version = "2.0.0-alpha.2", path = "../../../client/rpc" } +sc-rpc = { version = "2.0.0-alpha.5", path = "../../../client/rpc" } jsonrpc-core-client = { version = "14.0.3", features = ["http"] } hyper = "0.12.35" libp2p = "0.16.2" @@ -35,3 +34,6 @@ serde_json = "1.0" [features] bench = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/utils/subkey/src/main.rs b/bin/utils/subkey/src/main.rs index 33209692caffb87284318bc59962ab56bcb6cbf7..237cc68df2f4657808152d493d2e26ac9866231f 100644 --- a/bin/utils/subkey/src/main.rs +++ b/bin/utils/subkey/src/main.rs @@ -79,13 +79,15 @@ trait Crypto: Sized { ) where ::Public: PublicT, { + let v = network_override.unwrap_or_default(); if let Ok((pair, seed)) = Self::Pair::from_phrase(uri, password) { let public_key = Self::public_from_pair(&pair); - + match output { OutputType::Json => { let json = json!({ "secretPhrase": uri, + "networkId": String::from(v), "secretSeed": format_seed::(seed), "publicKey": format_public_key::(public_key.clone()), "accountId": format_account_id::(public_key), @@ -95,11 +97,13 @@ trait Crypto: Sized { }, OutputType::Text => { println!("Secret phrase `{}` is account:\n \ - Secret seed: {}\n \ - Public key (hex): {}\n \ - Account ID: {}\n \ - SS58 Address: {}", + Network ID/version: {}\n \ + Secret seed: {}\n \ + Public key (hex): {}\n \ + Account ID: {}\n \ + SS58 Address: {}", uri, + String::from(v), format_seed::(seed), format_public_key::(public_key.clone()), format_account_id::(public_key), @@ -114,6 +118,7 @@ trait Crypto: Sized { OutputType::Json => { let json = json!({ "secretKeyUri": uri, + "networkId": String::from(v), "secretSeed": if let Some(seed) = seed { format_seed::(seed) } else { "n/a".into() }, "publicKey": format_public_key::(public_key.clone()), "accountId": format_account_id::(public_key), @@ -123,11 +128,13 @@ trait Crypto: Sized { }, OutputType::Text => { println!("Secret Key URI `{}` is account:\n \ - Secret seed: {}\n \ - Public key (hex): {}\n \ - Account ID: {}\n \ - SS58 Address: {}", + Network ID/version: {}\n \ + Secret seed: {}\n \ + Public key (hex): {}\n \ + Account ID: {}\n \ + SS58 Address: {}", uri, + String::from(v), if let Some(seed) = seed { format_seed::(seed) } else { "n/a".into() }, format_public_key::(public_key.clone()), format_account_id::(public_key), @@ -135,7 +142,6 @@ trait Crypto: Sized { ); }, } - } else if let Ok((public_key, v)) = ::Public::from_string_with_version(uri) { @@ -167,7 +173,7 @@ trait Crypto: Sized { }, } } else { - println!("Invalid phrase/URI given"); + eprintln!("Invalid phrase/URI given"); } } } @@ -649,7 +655,7 @@ fn read_pair( } fn format_signature(signature: &SignatureOf) -> String { - format!("{}", hex::encode(signature)) + format!("{}", HexDisplay::from(&signature.as_ref())) } fn format_seed(seed: SeedOf) -> String { @@ -684,6 +690,7 @@ fn create_extrinsic( frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(f), Default::default(), + Default::default(), ) }; let raw_payload = SignedPayload::from_raw( @@ -697,6 +704,7 @@ fn create_extrinsic( (), (), (), + (), ), ); let signature = raw_payload.using_encoded(|payload| signer.sign(payload)).into_runtime(); @@ -712,7 +720,7 @@ fn create_extrinsic( } fn print_extrinsic(extrinsic: UncheckedExtrinsic) { - println!("0x{}", hex::encode(&extrinsic.encode())); + println!("0x{}", HexDisplay::from(&extrinsic.encode())); } fn print_usage(matches: &ArgMatches) { diff --git a/bin/utils/subkey/src/rpc.rs b/bin/utils/subkey/src/rpc.rs index 7b3cde595867bd46e49ad840178e407500dfdabc..e08ccc19a22b8de89088ffade2809f7418eb7d25 100644 --- a/bin/utils/subkey/src/rpc.rs +++ b/bin/utils/subkey/src/rpc.rs @@ -42,7 +42,7 @@ impl RpcClient { client.insert_key(key_type, suri, public).map(|_| ()) }) .map_err(|e| { - println!("Error inserting key: {:?}", e); + eprintln!("Error inserting key: {:?}", e); }) ); } diff --git a/bin/utils/subkey/src/vanity.rs b/bin/utils/subkey/src/vanity.rs index ff8703a9905ced71da4a6b4a6795f53fad6613d9..f921470946ec04732654091922711808f8aa6ad7 100644 --- a/bin/utils/subkey/src/vanity.rs +++ b/bin/utils/subkey/src/vanity.rs @@ -62,6 +62,12 @@ fn calculate_score(_desired: &str, key: &str) -> usize { 0 } +/// Validate whether the char is allowed to be used in base58. +/// num 0, lower l, upper I and O are not allowed. +fn validate_base58(c :char) -> bool { + c.is_alphanumeric() && !"0lIO".contains(c) +} + pub(super) fn generate_key(desired: &str) -> Result, &'static str> where PublicOf: PublicT, { @@ -69,7 +75,12 @@ pub(super) fn generate_key(desired: &str) -> Result, &'sta return Err("Pattern must not be empty"); } - println!("Generating key containing pattern '{}'", desired); + if !desired.chars().all(validate_base58) { + return Err("Pattern can only contains valid characters in base58 \ + (all alphanumeric except for 0, l, I and O)"); + } + + eprintln!("Generating key containing pattern '{}'", desired); let top = 45 + (desired.len() * 48); let mut best = 0; @@ -94,14 +105,14 @@ pub(super) fn generate_key(desired: &str) -> Result, &'sta score: score, }; if best >= top { - println!("best: {} == top: {}", best, top); + eprintln!("best: {} == top: {}", best, top); return Ok(keypair); } } done += 1; if done % good_waypoint(done) == 0 { - println!("{} keys searched; best is {}/{} complete", done, best, top); + eprintln!("{} keys searched; best is {}/{} complete", done, best, top); } } } @@ -162,6 +173,22 @@ mod tests { ); } + #[test] + fn test_invalid_pattern() { + assert!(generate_key::("").is_err()); + assert!(generate_key::("0").is_err()); + assert!(generate_key::("l").is_err()); + assert!(generate_key::("I").is_err()); + assert!(generate_key::("O").is_err()); + assert!(generate_key::("!").is_err()); + } + + #[test] + fn test_valid_pattern() { + assert!(generate_key::("o").is_ok()); + assert!(generate_key::("L").is_ok()); + } + #[cfg(feature = "bench")] #[bench] fn bench_paranoiac(b: &mut Bencher) { diff --git a/client/Cargo.toml b/client/Cargo.toml index 61199f04da970bbefb4f494e2d46a5824538d499..66ef4da458742858f7b4b92d4d35eae76055a325 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-client" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,37 +9,40 @@ repository = "https://github.com/paritytech/substrate/" description = "Substrate Client and associated logic." [dependencies] -sc-block-builder = { version = "0.8.0-alpha.2", path = "block-builder" } -sc-client-api = { version = "2.0.0-alpha.2", path = "api" } -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } -sp-consensus = { version = "0.8.0-alpha.2", path = "../primitives/consensus/common" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "block-builder" } +sc-client-api = { version = "2.0.0-alpha.5", path = "api" } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } +sp-consensus = { version = "0.8.0-alpha.5", path = "../primitives/consensus/common" } derive_more = { version = "0.99.2" } -sc-executor = { version = "0.8.0-alpha.2", path = "executor" } -sp-externalities = { version = "0.8.0-alpha.2", path = "../primitives/externalities" } +sc-executor = { version = "0.8.0-alpha.5", path = "executor" } +sp-externalities = { version = "0.8.0-alpha.5", path = "../primitives/externalities" } fnv = { version = "1.0.6" } futures = { version = "0.3.1", features = ["compat"] } hash-db = { version = "0.15.2" } hex-literal = { version = "0.2.1" } -sp-inherents = { version = "2.0.0-alpha.2", path = "../primitives/inherents" } -sp-keyring = { version = "2.0.0-alpha.2", path = "../primitives/keyring" } -kvdb = "0.4.0" +sp-inherents = { version = "2.0.0-alpha.5", path = "../primitives/inherents" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../primitives/keyring" } +kvdb = "0.5.0" log = { version = "0.4.8" } parking_lot = "0.10.0" -sp-core = { version = "2.0.0-alpha.2", path = "../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", path = "../primitives/std" } -sp-version = { version = "2.0.0-alpha.2", path = "../primitives/version" } -sp-api = { version = "2.0.0-alpha.2", path = "../primitives/api" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../primitives/runtime" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../primitives/blockchain" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../primitives/state-machine" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "telemetry" } -sp-trie = { version = "2.0.0-alpha.2", path = "../primitives/trie" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.8.0-alpha.2", path = "../utils/prometheus" } +sp-core = { version = "2.0.0-alpha.5", path = "../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", path = "../primitives/std" } +sp-version = { version = "2.0.0-alpha.5", path = "../primitives/version" } +sp-api = { version = "2.0.0-alpha.5", path = "../primitives/api" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../primitives/runtime" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../primitives/blockchain" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../primitives/state-machine" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "telemetry" } +sp-trie = { version = "2.0.0-alpha.5", path = "../primitives/trie" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.8.0-alpha.5", path = "../utils/prometheus" } tracing = "0.1.10" [dev-dependencies] env_logger = "0.7.0" tempfile = "3.1.0" substrate-test-runtime-client = { version = "2.0.0-dev", path = "../test-utils/runtime/client" } -kvdb-memorydb = "0.4.0" -sp-panic-handler = { version = "2.0.0-alpha.2", path = "../primitives/panic-handler" } +kvdb-memorydb = "0.5.0" +sp-panic-handler = { version = "2.0.0-alpha.5", path = "../primitives/panic-handler" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/api/Cargo.toml b/client/api/Cargo.toml index 7ceb12eaf60bb780bca061539852bc1a42c8819f..1d5b0be5e4a6d830958b70266a13c5fef811b8f2 100644 --- a/client/api/Cargo.toml +++ b/client/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-client-api" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,31 +11,34 @@ documentation = "https://docs.rs/sc-client-api" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../primitives/consensus/common" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/common" } derive_more = { version = "0.99.2" } -sc-executor = { version = "0.8.0-alpha.2", path = "../executor" } -sp-externalities = { version = "0.8.0-alpha.2", path = "../../primitives/externalities" } +sc-executor = { version = "0.8.0-alpha.5", path = "../executor" } +sp-externalities = { version = "0.8.0-alpha.5", path = "../../primitives/externalities" } fnv = { version = "1.0.6" } futures = { version = "0.3.1" } hash-db = { version = "0.15.2", default-features = false } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } hex-literal = { version = "0.2.1" } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/inherents" } -sp-keyring = { version = "2.0.0-alpha.2", path = "../../primitives/keyring" } -kvdb = "0.4.0" +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../../primitives/keyring" } +kvdb = "0.5.0" log = { version = "0.4.8" } parking_lot = "0.10.0" -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-version = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/version" } -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../primitives/state-machine" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "../telemetry" } -sp-trie = { version = "2.0.0-alpha.2", path = "../../primitives/trie" } -sp-storage = { version = "2.0.0-alpha.2", path = "../../primitives/storage" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../primitives/transaction-pool" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-version = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/version" } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../primitives/state-machine" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../telemetry" } +sp-trie = { version = "2.0.0-alpha.5", path = "../../primitives/trie" } +sp-storage = { version = "2.0.0-alpha.5", path = "../../primitives/storage" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../primitives/transaction-pool" } [dev-dependencies] sp-test-primitives = { version = "2.0.0-dev", path = "../../primitives/test-primitives" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index a7ea523552d8f7ac1a51c903d23b06fd73dd9181..3fe4de13e33b9daa1e37083dcfd9b53bb69ade84 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-authority-discovery" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" build = "build.rs" @@ -14,27 +14,30 @@ prost-build = "0.6.1" [dependencies] bytes = "0.5.0" -codec = { package = "parity-scale-codec", default-features = false, version = "1.2.0" } +codec = { package = "parity-scale-codec", default-features = false, version = "1.3.0" } derive_more = "0.99.2" -futures = "0.3.1" +futures = "0.3.4" futures-timer = "3.0.1" libp2p = { version = "0.16.2", default-features = false, features = ["secp256k1", "libp2p-websocket"] } log = "0.4.8" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.8.0-alpha.2" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.8.0-alpha.5"} prost = "0.6.1" rand = "0.7.2" -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sc-keystore = { version = "2.0.0-alpha.2", path = "../keystore" } -sc-network = { version = "0.8.0-alpha.2", path = "../network" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../keystore" } +sc-network = { version = "0.8.0-alpha.5", path = "../network" } serde_json = "1.0.41" -sp-authority-discovery = { version = "2.0.0-alpha.2", path = "../../primitives/authority-discovery" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api" } +sp-authority-discovery = { version = "2.0.0-alpha.5", path = "../../primitives/authority-discovery" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api" } [dev-dependencies] env_logger = "0.7.0" quickcheck = "0.9.0" -sc-peerset = { version = "2.0.0-alpha.2", path = "../peerset" } +sc-peerset = { version = "2.0.0-alpha.5", path = "../peerset" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client"} + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/authority-discovery/src/error.rs b/client/authority-discovery/src/error.rs index 74b7043c29ba0c3fb8439c6dde47c619183831ed..216f8c26bf07e397a80612c17ca8ea82cd2275da 100644 --- a/client/authority-discovery/src/error.rs +++ b/client/authority-discovery/src/error.rs @@ -16,6 +16,8 @@ //! Authority discovery errors. +use sp_core::crypto::CryptoTypePublicPair; + /// AuthorityDiscovery Result. pub type Result = std::result::Result; @@ -46,6 +48,10 @@ pub enum Error { EncodingDecodingScale(codec::Error), /// Failed to parse a libp2p multi address. ParsingMultiaddress(libp2p::core::multiaddr::Error), + /// Failed to sign using a specific public key + MissingSignature(CryptoTypePublicPair), + /// Failed to sign using all public keys + Signing, /// Failed to register Prometheus metric. Prometheus(prometheus_endpoint::PrometheusError), } diff --git a/client/authority-discovery/src/lib.rs b/client/authority-discovery/src/lib.rs index 171a401bbbf8b5d00bb80d06ee0dbe2ad7d482ce..ec103ab8a0ea1f2048358cca9515decb464dfe84 100644 --- a/client/authority-discovery/src/lib.rs +++ b/client/authority-discovery/src/lib.rs @@ -63,7 +63,7 @@ use prost::Message; use sc_client_api::blockchain::HeaderBackend; use sc_network::{DhtEvent, ExHashT, NetworkStateInfo}; use sp_authority_discovery::{AuthorityDiscoveryApi, AuthorityId, AuthoritySignature, AuthorityPair}; -use sp_core::crypto::{key_types, Pair}; +use sp_core::crypto::{key_types, CryptoTypePublicPair, Pair}; use sp_core::traits::BareCryptoStorePtr; use sp_runtime::{traits::Block as BlockT, generic::BlockId}; use sp_api::ProvideRuntimeApi; @@ -283,19 +283,36 @@ where .encode(&mut serialized_addresses) .map_err(Error::EncodingProto)?; - for key in self.get_priv_keys_within_authority_set()?.into_iter() { - let signature = key.sign(&serialized_addresses); + let keys: Vec = self.get_own_public_keys_within_authority_set()? + .into_iter() + .map(Into::into) + .collect(); + let signatures = self.key_store + .read() + .sign_with_all( + key_types::AUTHORITY_DISCOVERY, + keys.clone(), + serialized_addresses.as_slice(), + ) + .map_err(|_| Error::Signing)?; + + for (sign_result, key) in signatures.iter().zip(keys) { let mut signed_addresses = vec![]; + + // sign_with_all returns Result signature + // is generated for a public key that is supported. + // Verify that all signatures exist for all provided keys. + let signature = sign_result.as_ref().map_err(|_| Error::MissingSignature(key.clone()))?; schema::SignedAuthorityAddresses { addresses: serialized_addresses.clone(), - signature: signature.encode(), + signature: Encode::encode(&signature), } .encode(&mut signed_addresses) .map_err(Error::EncodingProto)?; self.network.put_value( - hash_authority_id(key.public().as_ref())?, + hash_authority_id(key.1.as_ref())?, signed_addresses, ); } @@ -446,21 +463,6 @@ where Ok(()) } - /// Retrieve all local authority discovery private keys that are within the current authority - /// set. - fn get_priv_keys_within_authority_set(&mut self) -> Result> { - let keys = self.get_own_public_keys_within_authority_set()? - .into_iter() - .map(std::convert::Into::into) - .filter_map(|pub_key| { - self.key_store.read().sr25519_key_pair(key_types::AUTHORITY_DISCOVERY, &pub_key) - }) - .map(std::convert::Into::into) - .collect(); - - Ok(keys) - } - /// Retrieve our public keys within the current authority set. // // A node might have multiple authority discovery keys within its keystore, e.g. an old one and diff --git a/client/authority-discovery/src/tests.rs b/client/authority-discovery/src/tests.rs index 1296c2b627a1eab7c966744ae79d3bf2557e1456..3178a06d905b2ab817c98dc9d2933c0037c22099 100644 --- a/client/authority-discovery/src/tests.rs +++ b/client/authority-discovery/src/tests.rs @@ -21,8 +21,8 @@ use futures::executor::block_on; use futures::future::poll_fn; use libp2p::{kad, PeerId}; -use sp_api::{ApiExt, ApiErrorExt, Core, RuntimeVersion, StorageProof, ProvideRuntimeApi, ApiRef}; -use sp_core::{testing::KeyStore, ExecutionContext, NativeOrEncoded}; +use sp_api::{ProvideRuntimeApi, ApiRef}; +use sp_core::testing::KeyStore; use sp_runtime::traits::{Zero, Block as BlockT, NumberFor}; use substrate_test_runtime_client::runtime::Block; @@ -99,8 +99,7 @@ impl ProvideRuntimeApi for TestApi { fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { RuntimeApi { authorities: self.authorities.clone(), - } - .into() + }.into() } } @@ -149,90 +148,13 @@ struct RuntimeApi { authorities: Vec, } -impl Core for RuntimeApi { - fn Core_version_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - _: Option<()>, - _: Vec, - ) -> std::result::Result, sp_blockchain::Error> { - unimplemented!("Not required for testing!") - } - - fn Core_execute_block_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - _: Option, - _: Vec, - ) -> std::result::Result, sp_blockchain::Error> { - unimplemented!("Not required for testing!") - } +sp_api::mock_impl_runtime_apis! { + impl AuthorityDiscoveryApi for RuntimeApi { + type Error = sp_blockchain::Error; - fn Core_initialize_block_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - _: Option<&::Header>, - _: Vec, - ) -> std::result::Result, sp_blockchain::Error> { - unimplemented!("Not required for testing!") - } -} - -impl ApiErrorExt for RuntimeApi { - type Error = sp_blockchain::Error; -} - -impl ApiExt for RuntimeApi { - type StateBackend = < - substrate_test_runtime_client::Backend as sc_client_api::backend::Backend - >::State; - - fn map_api_result std::result::Result, R, E>( - &self, - _: F - ) -> std::result::Result { - unimplemented!("Not required for testing!") - } - - fn runtime_version_at( - &self, - _: &BlockId, - ) -> std::result::Result { - unimplemented!("Not required for testing!") - } - - fn record_proof(&mut self) { - unimplemented!("Not required for testing!") - } - - fn extract_proof(&mut self) -> Option { - unimplemented!("Not required for testing!") - } - - fn into_storage_changes( - &self, - _: &Self::StateBackend, - _: Option<&sp_api::ChangesTrieState, sp_api::NumberFor>>, - _: ::Hash, - ) -> std::result::Result, String> - where Self: Sized - { - unimplemented!("Not required for testing!") - } -} - -impl AuthorityDiscoveryApi for RuntimeApi { - fn AuthorityDiscoveryApi_authorities_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - _: Option<()>, - _: Vec, - ) -> std::result::Result>, sp_blockchain::Error> { - return Ok(NativeOrEncoded::Native(self.authorities.clone())); + fn authorities(&self) -> Vec { + self.authorities.clone() + } } } diff --git a/client/basic-authorship/Cargo.toml b/client/basic-authorship/Cargo.toml index 7503221f6556072b8b45d69b5b1f420397638a9c..040370ac491cf09932ef1e7467ebeb96847b5af8 100644 --- a/client/basic-authorship/Cargo.toml +++ b/client/basic-authorship/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-basic-authorship" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,21 +10,25 @@ description = "Basic implementation of block-authoring logic." [dependencies] log = "0.4.8" -futures = "0.3.1" -codec = { package = "parity-scale-codec", version = "1.2.0" } -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../primitives/consensus/common" } -sp-inherents = { version = "2.0.0-alpha.2", path = "../../primitives/inherents" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "../telemetry" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../primitives/transaction-pool" } -sc-block-builder = { version = "0.8.0-alpha.2", path = "../block-builder" } +futures = "0.3.4" +codec = { package = "parity-scale-codec", version = "1.3.0" } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/common" } +sp-inherents = { version = "2.0.0-alpha.5", path = "../../primitives/inherents" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../telemetry" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../primitives/transaction-pool" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "../block-builder" } tokio-executor = { version = "0.2.0-alpha.6", features = ["blocking"] } +futures-timer = "3.0.1" [dev-dependencies] -sc-transaction-pool = { version = "2.0.0-alpha.2", path = "../../client/transaction-pool" } +sc-transaction-pool = { version = "2.0.0-alpha.5", path = "../../client/transaction-pool" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client" } parking_lot = "0.10.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index fc9a5dec0668dce922529277ba0ccb13eed0ad73..a24ba312420aeb5948f7c76414b1db1d547925d5 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -33,7 +33,7 @@ use sp_transaction_pool::{TransactionPool, InPoolTransaction}; use sc_telemetry::{telemetry, CONSENSUS_INFO}; use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; use sp_api::{ProvideRuntimeApi, ApiExt}; -use futures::prelude::*; +use futures::{executor, future, future::Either}; use sp_blockchain::{HeaderBackend, ApplyExtrinsicFailed}; use std::marker::PhantomData; @@ -76,7 +76,7 @@ impl ProposerFactory let id = BlockId::hash(parent_hash); - info!("Starting consensus session on top of parent {:?}", parent_hash); + info!("🙌 Starting consensus session on top of parent {:?}", parent_hash); let proposer = Proposer { inner: Arc::new(ProposerInner { @@ -210,7 +210,18 @@ impl ProposerInner let mut is_first = true; let mut skipped = 0; let mut unqueue_invalid = Vec::new(); - let pending_iterator = self.transaction_pool.ready(); + let pending_iterator = match executor::block_on(future::select( + self.transaction_pool.ready_at(self.parent_number), + futures_timer::Delay::new((deadline - (self.now)()) / 8), + )) { + Either::Left((iterator, _)) => iterator, + Either::Right(_) => { + log::warn!( + "Timeout fired waiting for transaction pool to be ready. Proceeding to block production anyway.", + ); + self.transaction_pool.ready() + } + }; debug!("Attempting to push transactions from the pool."); debug!("Pool status: {:?}", self.transaction_pool.status()); @@ -266,7 +277,7 @@ impl ProposerInner let (block, storage_changes, proof) = block_builder.build()?.into_inner(); - info!("Prepared block for proposing at {} [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]", + info!("🎁 Prepared block for proposing at {} [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]", block.header().number(), ::Hash::from(block.header().hash()), block.header().parent_hash(), @@ -304,10 +315,14 @@ mod tests { prelude::*, runtime::{Extrinsic, Transfer}, }; + use sp_transaction_pool::{ChainEvent, MaintainedTransactionPool, TransactionSource}; use sc_transaction_pool::{BasicPool, FullChainApi}; use sp_api::Core; use backend::Backend; use sp_blockchain::HeaderBackend; + use sp_runtime::traits::NumberFor; + + const SOURCE: TransactionSource = TransactionSource::External; fn extrinsic(nonce: u64) -> Extrinsic { Transfer { @@ -318,6 +333,17 @@ mod tests { }.into_signed_tx() } + fn chain_event(block_number: u64, header: B::Header) -> ChainEvent + where NumberFor: From + { + ChainEvent::NewBlock { + id: BlockId::Number(block_number.into()), + retracted: vec![], + is_new_best: true, + header, + } + } + #[test] fn should_cease_building_block_when_deadline_is_reached() { // given @@ -327,19 +353,30 @@ mod tests { ); futures::executor::block_on( - txpool.submit_at(&BlockId::number(0), vec![extrinsic(0), extrinsic(1)]) + txpool.submit_at(&BlockId::number(0), SOURCE, vec![extrinsic(0), extrinsic(1)]) ).unwrap(); + futures::executor::block_on( + txpool.maintain(chain_event( + 0, + client.header(&BlockId::Number(0u64)).expect("header get error").expect("there should be header") + )) + ); + let mut proposer_factory = ProposerFactory::new(client.clone(), txpool.clone()); - let cell = Mutex::new(time::Instant::now()); + let cell = Mutex::new((false, time::Instant::now())); let mut proposer = proposer_factory.init_with_now( &client.header(&BlockId::number(0)).unwrap().unwrap(), Box::new(move || { let mut value = cell.lock(); - let old = *value; + if !value.0 { + value.0 = true; + return value.1; + } + let old = value.1; let new = old + time::Duration::from_secs(2); - *value = new; + *value = (true, new); old }) ); @@ -368,9 +405,16 @@ mod tests { let block_id = BlockId::Hash(genesis_hash); futures::executor::block_on( - txpool.submit_at(&BlockId::number(0), vec![extrinsic(0)]), + txpool.submit_at(&BlockId::number(0), SOURCE, vec![extrinsic(0)]), ).unwrap(); + futures::executor::block_on( + txpool.maintain(chain_event( + 0, + client.header(&BlockId::Number(0u64)).expect("header get error").expect("there should be header") + )) + ); + let mut proposer_factory = ProposerFactory::new(client.clone(), txpool.clone()); let mut proposer = proposer_factory.init_with_now( @@ -412,7 +456,7 @@ mod tests { ); futures::executor::block_on( - txpool.submit_at(&BlockId::number(0), vec![ + txpool.submit_at(&BlockId::number(0), SOURCE, vec![ extrinsic(0), extrinsic(1), Transfer { @@ -459,15 +503,26 @@ mod tests { block }; + futures::executor::block_on( + txpool.maintain(chain_event( + 0, + client.header(&BlockId::Number(0u64)).expect("header get error").expect("there should be header") + )) + ); + // let's create one block and import it let block = propose_block(&client, 0, 2, 7); client.import(BlockOrigin::Own, block).unwrap(); - // now let's make sure that we can still make some progress + futures::executor::block_on( + txpool.maintain(chain_event( + 1, + client.header(&BlockId::Number(1)).expect("header get error").expect("there should be header") + )) + ); - // This is most likely incorrect, and caused by #5139 - let tx_remaining = 0; - let block = propose_block(&client, 1, 2, tx_remaining); + // now let's make sure that we can still make some progress + let block = propose_block(&client, 1, 2, 5); client.import(BlockOrigin::Own, block).unwrap(); } } diff --git a/client/block-builder/Cargo.toml b/client/block-builder/Cargo.toml index dd4ebcb07f889fa962d27ebf776572b21ae98e62..5f9ef7c75e8591baaada2b2ff1a46fbebc72f231 100644 --- a/client/block-builder/Cargo.toml +++ b/client/block-builder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-block-builder" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,16 +10,19 @@ description = "Substrate block builder" [dependencies] -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../primitives/state-machine" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../primitives/consensus/common" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-block-builder = { version = "2.0.0-alpha.2", path = "../../primitives/block-builder" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../primitives/state-machine" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/common" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-block-builder = { version = "2.0.0-alpha.5", path = "../../primitives/block-builder" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } [dev-dependencies] substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } -sp-trie = { version = "2.0.0-alpha.2", path = "../../primitives/trie" } +sp-trie = { version = "2.0.0-alpha.5", path = "../../primitives/trie" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/chain-spec/Cargo.toml b/client/chain-spec/Cargo.toml index 10d4fc2622480c70dbbd938e3210b1421c3e0f84..5d65cbd842b5430064ae2249fe63f9f0b4cf2b8c 100644 --- a/client/chain-spec/Cargo.toml +++ b/client/chain-spec/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-chain-spec" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,11 +9,14 @@ repository = "https://github.com/paritytech/substrate/" description = "Substrate chain configurations." [dependencies] -sc-chain-spec-derive = { version = "2.0.0-alpha.2", path = "./derive" } +sc-chain-spec-derive = { version = "2.0.0-alpha.5", path = "./derive" } impl-trait-for-tuples = "0.1.3" -sc-network = { version = "0.8.0-alpha.2", path = "../network" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +sc-network = { version = "0.8.0-alpha.5", path = "../network" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } serde = { version = "1.0.101", features = ["derive"] } serde_json = "1.0.41" -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "../telemetry" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../telemetry" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/chain-spec/derive/Cargo.toml b/client/chain-spec/derive/Cargo.toml index df0e2f92b2e94a2bfbf47d5fd63214925919c14d..9343c9a6de1c95919abe695b4918bd576117efbf 100644 --- a/client/chain-spec/derive/Cargo.toml +++ b/client/chain-spec/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-chain-spec-derive" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -18,3 +18,6 @@ quote = "1.0.3" syn = "1.0.7" [dev-dependencies] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/chain-spec/src/chain_spec.rs b/client/chain-spec/src/chain_spec.rs index 008af6f7e0f0f73e2b209ba237d745d5606c946e..33f217cb4e7d86b71a600d75a898d5010c564d35 100644 --- a/client/chain-spec/src/chain_spec.rs +++ b/client/chain-spec/src/chain_spec.rs @@ -318,7 +318,7 @@ impl ChainSpec { impl crate::ChainSpec for ChainSpec where G: RuntimeGenesis, - E: GetExtension + serde::Serialize + Clone, + E: GetExtension + serde::Serialize + Clone + Send, { fn boot_nodes(&self) -> &[String] { ChainSpec::boot_nodes(self) diff --git a/client/chain-spec/src/lib.rs b/client/chain-spec/src/lib.rs index 509524fd0aeb7e46e691c1a2afdde6ad1279ea42..353c386f3679e9e8e711007f249b2c62ba492cfc 100644 --- a/client/chain-spec/src/lib.rs +++ b/client/chain-spec/src/lib.rs @@ -125,7 +125,7 @@ pub trait RuntimeGenesis: Serialize + DeserializeOwned + BuildStorage {} impl RuntimeGenesis for T {} /// Common interface to `GenericChainSpec` -pub trait ChainSpec: BuildStorage { +pub trait ChainSpec: BuildStorage + Send { /// Spec name. fn name(&self) -> &str; /// Spec id. diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 908cc4c10eab642e76b1085f08b5ba57415443e7..b5d7213e4fcd128d20fd08618cc557db4ddd38bc 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-cli" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Substrate CLI interface." edition = "2018" @@ -20,30 +20,33 @@ ansi_term = "0.12.1" lazy_static = "1.4.0" app_dirs = "1.2.1" tokio = { version = "0.2.9", features = [ "signal", "rt-core", "rt-threaded" ] } -futures = "0.3.1" -fdlimit = "0.1.1" +futures = "0.3.4" +fdlimit = "0.1.4" serde_json = "1.0.41" -sc-informant = { version = "0.8.0-alpha.2", path = "../informant" } -sp-panic-handler = { version = "2.0.0-alpha.2", path = "../../primitives/panic-handler" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -sc-network = { version = "0.8.0-alpha.2", path = "../network" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sc-service = { version = "0.8.0-alpha.2", default-features = false, path = "../service" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../primitives/state-machine" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "../telemetry" } -substrate-prometheus-endpoint = { path = "../../utils/prometheus" , version = "0.8.0-alpha.2"} -sp-keyring = { version = "2.0.0-alpha.2", path = "../../primitives/keyring" } +sc-informant = { version = "0.8.0-alpha.5", path = "../informant" } +sp-panic-handler = { version = "2.0.0-alpha.5", path = "../../primitives/panic-handler" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +sc-network = { version = "0.8.0-alpha.5", path = "../network" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sc-service = { version = "0.8.0-alpha.5", default-features = false, path = "../service" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../primitives/state-machine" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../telemetry" } +substrate-prometheus-endpoint = { path = "../../utils/prometheus" , version = "0.8.0-alpha.5"} +sp-keyring = { version = "2.0.0-alpha.5", path = "../../primitives/keyring" } names = "0.11.0" structopt = "0.3.8" -sc-tracing = { version = "2.0.0-alpha.2", path = "../tracing" } +sc-tracing = { version = "2.0.0-alpha.5", path = "../tracing" } chrono = "0.4.10" -parity-util-mem = { version = "0.5.2", default-features = false, features = ["primitive-types"] } +parity-util-mem = { version = "0.6.0", default-features = false, features = ["primitive-types"] } [target.'cfg(not(target_os = "unknown"))'.dependencies] rpassword = "4.0.1" +[target.'cfg(target_family = "unix")'.dependencies] +nix = "0.17.0" + [dev-dependencies] tempfile = "3.1.0" @@ -51,3 +54,6 @@ tempfile = "3.1.0" wasmtime = [ "sc-service/wasmtime", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/cli/src/commands/mod.rs b/client/cli/src/commands/mod.rs index 50e34856f12d61a833d2c7f75742ddaa65f733c3..d87b08f7f49828ef48bb469c016d2ac65e507906 100644 --- a/client/cli/src/commands/mod.rs +++ b/client/cli/src/commands/mod.rs @@ -50,7 +50,7 @@ const DEFAULT_NETWORK_CONFIG_PATH : &'static str = "network"; /// `Run` are exported as main executable parameters. #[derive(Debug, Clone, StructOpt)] pub enum Subcommand { - /// Build a spec.json file, outputing to stdout. + /// Build a spec.json file, outputs to stdout. BuildSpec(build_spec_cmd::BuildSpecCmd), /// Export blocks to a file. @@ -70,7 +70,7 @@ pub enum Subcommand { } impl Subcommand { - /// Get the shared parameters of a `CoreParams` command + /// Get the shared parameters of a `CoreParams` command. pub fn get_shared_params(&self) -> &SharedParams { use Subcommand::*; @@ -84,7 +84,7 @@ impl Subcommand { } } - /// Run any `CoreParams` command + /// Run any `CoreParams` command. pub fn run( self, config: Configuration, @@ -107,7 +107,7 @@ impl Subcommand { } } - /// Update and prepare a `Configuration` with command line parameters + /// Update and prepare a `Configuration` with command line parameters. pub fn update_config( &self, mut config: &mut Configuration, diff --git a/client/cli/src/commands/runcmd.rs b/client/cli/src/commands/runcmd.rs index 354448636342802dfe84699ca3d6fa517aef931c..8a6d675d32aeca9ce4ed7ba14db5560ab7de5433 100644 --- a/client/cli/src/commands/runcmd.rs +++ b/client/cli/src/commands/runcmd.rs @@ -82,7 +82,7 @@ pub struct RunCmd { )] pub sentry: bool, - /// Disable GRANDPA voter when running in validator mode, otherwise disables the GRANDPA observer. + /// Disable GRANDPA voter when running in validator mode, otherwise disable the GRANDPA observer. #[structopt(long = "no-grandpa")] pub no_grandpa: bool, @@ -92,7 +92,7 @@ pub struct RunCmd { /// Listen to all RPC interfaces. /// - /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use a RPC proxy + /// 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: https://github.com/paritytech/substrate/wiki/Public-RPC. /// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks. #[structopt(long = "rpc-external")] @@ -106,7 +106,7 @@ pub struct RunCmd { /// Listen to all Websocket interfaces. /// - /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use a RPC proxy + /// 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: https://github.com/paritytech/substrate/wiki/Public-RPC. /// Use `--unsafe-ws-external` to suppress the warning if you understand the risks. #[structopt(long = "ws-external")] @@ -140,7 +140,7 @@ pub struct RunCmd { /// /// 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 https://polkadot.js.org origins. When running in + /// allow localhost and https://polkadot.js.org 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))] pub rpc_cors: Option, @@ -169,7 +169,7 @@ pub struct RunCmd { /// The URL of the telemetry server to connect to. /// - /// This flag can be passed multiple times as a mean to specify multiple + /// This flag can be passed multiple times as a means to specify multiple /// telemetry endpoints. Verbosity levels range from 0-9, with 0 denoting /// the least verbosity. If no verbosity level is specified the default is /// 0. @@ -275,7 +275,7 @@ pub struct RunCmd { } impl RunCmd { - /// Get the `Sr25519Keyring` matching one of the flag + /// Get the `Sr25519Keyring` matching one of the flag. pub fn get_keyring(&self) -> Option { use sp_keyring::Sr25519Keyring::*; @@ -290,7 +290,7 @@ impl RunCmd { else { None } } - /// Update and prepare a `Configuration` with command line parameters of `RunCmd` and `VersionInfo` + /// Update and prepare a `Configuration` with command line parameters of `RunCmd` and `VersionInfo`. pub fn update_config( &self, mut config: &mut Configuration, @@ -419,7 +419,7 @@ impl RunCmd { config.telemetry_endpoints = None; } else if !self.telemetry_endpoints.is_empty() { config.telemetry_endpoints = Some( - TelemetryEndpoints::new(self.telemetry_endpoints.clone()) + TelemetryEndpoints::new(self.telemetry_endpoints.clone()).map_err(|e| e.to_string())? ); } @@ -444,7 +444,7 @@ impl RunCmd { Ok(()) } - /// Run the command that runs the node + /// Run the command that runs the node. pub fn run( self, config: Configuration, @@ -461,9 +461,9 @@ impl RunCmd { info!("{}", version.name); info!(" version {}", config.full_version()); info!(" by {}, {}-{}", version.author, version.copyright_start_year, Local::today().year()); - info!("Chain specification: {}", config.expect_chain_spec().name()); - info!("Node name: {}", config.name); - info!("Roles: {}", config.display_role()); + info!("📋 Chain specification: {}", config.expect_chain_spec().name()); + info!("🏷 Node name: {}", config.name); + info!("👤 Roles: {}", config.display_role()); match config.roles { Roles::LIGHT => run_service_until_exit( @@ -489,7 +489,7 @@ impl RunCmd { } } -/// Check whether a node name is considered as valid +/// Check whether a node name is considered as valid. pub fn is_node_name_valid(_name: &str) -> Result<(), &str> { let name = _name.to_string(); if name.chars().count() >= NODE_NAME_MAX_LENGTH { @@ -586,7 +586,7 @@ fn parse_telemetry_endpoints(s: &str) -> Result<(String, u8), Box), @@ -601,7 +601,7 @@ impl From for Option> { } } -/// Parse cors origins +/// Parse cors origins. fn parse_cors(s: &str) -> Result> { let mut is_all = false; let mut origins = Vec::new(); @@ -689,7 +689,8 @@ mod tests { "test-id", || (), vec!["boo".to_string()], - Some(TelemetryEndpoints::new(vec![("foo".to_string(), 42)])), + Some(TelemetryEndpoints::new(vec![("wss://foo/bar".to_string(), 42)]) + .expect("provided url should be valid")), None, None, None::<()>, diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index e28edebd60def53ba34497d416a9bea7919ef983..18a5991e39b92a6e2987ffb0eb75a93c10e517c3 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -211,7 +211,7 @@ pub fn init_logger(pattern: &str) { }); if builder.try_init().is_err() { - info!("Not registering Substrate logger, as there is already a global logger registered!"); + info!("💬 Not registering Substrate logger, as there is already a global logger registered!"); } } @@ -221,3 +221,21 @@ fn kill_color(s: &str) -> String { } RE.replace_all(s, "").to_string() } + +/// Reset the signal pipe (`SIGPIPE`) handler to the default one provided by the system. +/// This will end the program on `SIGPIPE` instead of panicking. +/// +/// This should be called before calling any cli method or printing any output. +pub fn reset_signal_pipe_handler() -> Result<()> { + #[cfg(target_family = "unix")] + { + use nix::sys::signal; + + unsafe { + signal::signal(signal::Signal::SIGPIPE, signal::SigHandler::SigDfl) + .map_err(|e| Error::Other(e.to_string()))?; + } + } + + Ok(()) +} diff --git a/client/cli/src/params/import_params.rs b/client/cli/src/params/import_params.rs index 2b826d69c7fb22269f893a7154b35ce6579e9447..b647feeece30b8c34b74b0a33633f5380bfb21fd 100644 --- a/client/cli/src/params/import_params.rs +++ b/client/cli/src/params/import_params.rs @@ -62,11 +62,11 @@ pub struct ImportParams { #[structopt(long = "state-cache-size", value_name = "Bytes", default_value = "67108864")] pub state_cache_size: usize, - /// Comma separated list of targets for tracing + /// Comma separated list of targets for tracing. #[structopt(long = "tracing-targets", value_name = "TARGETS")] pub tracing_targets: Option, - /// Receiver to process tracing messages + /// Receiver to process tracing messages. #[structopt( long = "tracing-receiver", value_name = "RECEIVER", diff --git a/client/cli/src/params/network_configuration_params.rs b/client/cli/src/params/network_configuration_params.rs index 974fa0be937a70dd117914b04aaaa31031705cf2..4de5e44fb5ace05f79b7c86739f2637b2fc650f0 100644 --- a/client/cli/src/params/network_configuration_params.rs +++ b/client/cli/src/params/network_configuration_params.rs @@ -83,9 +83,9 @@ pub struct NetworkConfigurationParams { #[structopt(long = "no-mdns")] pub no_mdns: bool, - /// Maximum number of peers to ask the same blocks in parallel. + /// Maximum number of peers from which to ask for the same blocks in parallel. /// - /// This allows downlading announced blocks from multiple peers. Decrease to save + /// 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")] pub max_parallel_downloads: u32, diff --git a/client/cli/src/params/node_key_params.rs b/client/cli/src/params/node_key_params.rs index aef3af94c77f29b7b0401f55133ca621448907aa..c55ec8ee692db5715c88414c905f87fc20ca51fa 100644 --- a/client/cli/src/params/node_key_params.rs +++ b/client/cli/src/params/node_key_params.rs @@ -38,7 +38,7 @@ pub struct NodeKeyParams { /// `--node-key-type` as follows: /// /// `ed25519`: - /// The value is parsed as a hex-encoded Ed25519 32 bytes secret key, + /// The value is parsed as a hex-encoded Ed25519 32 byte secret key, /// i.e. 64 hex characters. /// /// The value of this option takes precedence over `--node-key-file`. @@ -82,7 +82,7 @@ pub struct NodeKeyParams { /// as follows: /// /// `ed25519`: - /// The file must contain an unencoded 32 bytes Ed25519 secret key. + /// The file must contain an unencoded 32 byte Ed25519 secret key. /// /// If the file does not exist, it is created with a newly generated secret key of /// the chosen type. diff --git a/client/cli/src/params/shared_params.rs b/client/cli/src/params/shared_params.rs index 310e3de5da8d0c54b58bc478667aa273b93df72b..41b9cce8264cc0ce46de7d81661b396f4275266b 100644 --- a/client/cli/src/params/shared_params.rs +++ b/client/cli/src/params/shared_params.rs @@ -30,7 +30,7 @@ const DEFAULT_DB_CONFIG_PATH : &'static str = "db"; /// Shared parameters used by all `CoreParams`. #[derive(Debug, StructOpt, Clone)] pub struct SharedParams { - /// Specify the chain specification (one of dev, local or staging). + /// Specify the chain specification (one of dev, local, or staging). #[structopt(long = "chain", value_name = "CHAIN_SPEC")] pub chain: Option, @@ -42,7 +42,10 @@ pub struct SharedParams { #[structopt(long = "base-path", short = "d", value_name = "PATH", parse(from_os_str))] pub base_path: Option, - /// Sets a custom logging filter. + /// 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 = "log", value_name = "LOG_PATTERN")] pub log: Option, } diff --git a/client/consensus/aura/Cargo.toml b/client/consensus/aura/Cargo.toml index f0b90f381932b6b34721bbc33764c3a5f57d741f..f0fe368acbb037f0b9181263889baa31d3e3f660 100644 --- a/client/consensus/aura/Cargo.toml +++ b/client/consensus/aura/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-aura" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Aura consensus algorithm for substrate" edition = "2018" @@ -9,37 +9,40 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-application-crypto = { version = "2.0.0-alpha.2", path = "../../../primitives/application-crypto" } -sp-consensus-aura = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/aura" } -sp-block-builder = { version = "2.0.0-alpha.2", path = "../../../primitives/block-builder" } -sc-block-builder = { version = "0.8.0-alpha.3", path = "../../../client/block-builder" } -sc-client = { version = "0.8.0-alpha.2", path = "../../" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../../api" } -codec = { package = "parity-scale-codec", version = "1.2.0" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } +sp-application-crypto = { version = "2.0.0-alpha.5", path = "../../../primitives/application-crypto" } +sp-consensus-aura = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/aura" } +sp-block-builder = { version = "2.0.0-alpha.5", path = "../../../primitives/block-builder" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "../../../client/block-builder" } +sc-client = { version = "0.8.0-alpha.5", path = "../../" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../api" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } derive_more = "0.99.2" -futures = "0.3.1" +futures = "0.3.4" futures-timer = "3.0.1" -sp-inherents = { version = "2.0.0-alpha.2", path = "../../../primitives/inherents" } -sc-keystore = { version = "2.0.0-alpha.2", path = "../../keystore" } +sp-inherents = { version = "2.0.0-alpha.5", path = "../../../primitives/inherents" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../../keystore" } log = "0.4.8" parking_lot = "0.10.0" -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -sp-io = { version = "2.0.0-alpha.2", path = "../../../primitives/io" } -sp-version = { version = "2.0.0-alpha.2", path = "../../../primitives/version" } -sc-consensus-slots = { version = "0.8.0-alpha.2", path = "../slots" } -sp-api = { version = "2.0.0-alpha.2", path = "../../../primitives/api" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-timestamp = { version = "2.0.0-alpha.2", path = "../../../primitives/timestamp" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "../../telemetry" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +sp-io = { version = "2.0.0-alpha.5", path = "../../../primitives/io" } +sp-version = { version = "2.0.0-alpha.5", path = "../../../primitives/version" } +sc-consensus-slots = { version = "0.8.0-alpha.5", path = "../slots" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../primitives/api" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-timestamp = { version = "2.0.0-alpha.5", path = "../../../primitives/timestamp" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../../telemetry" } [dev-dependencies] -sp-keyring = { version = "2.0.0-alpha.2", path = "../../../primitives/keyring" } -sc-executor = { version = "0.8.0-alpha.2", path = "../../executor" } -sc-network = { version = "0.8.0-alpha.2", path = "../../network" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../../../primitives/keyring" } +sc-executor = { version = "0.8.0-alpha.5", path = "../../executor" } +sc-network = { version = "0.8.0-alpha.5", path = "../../network" } sc-network-test = { version = "0.8.0-dev", path = "../../network/test" } -sc-service = { version = "0.8.0-alpha.2", path = "../../service" } +sc-service = { version = "0.8.0-alpha.5", path = "../../service" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../../test-utils/runtime/client" } env_logger = "0.7.0" tempfile = "3.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index 66455adbdf8284a822a98a4bf4a2314964445fe5..19c5bf9e588b3d51f933855ddd01e1a80cd6eae9 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-babe" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "BABE consensus algorithm for substrate" edition = "2018" @@ -9,53 +9,56 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-consensus-babe" - [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } -sp-consensus-babe = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/babe" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-application-crypto = { version = "2.0.0-alpha.2", path = "../../../primitives/application-crypto" } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } +sp-consensus-babe = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/babe" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-application-crypto = { version = "2.0.0-alpha.5", path = "../../../primitives/application-crypto" } num-bigint = "0.2.3" num-rational = "0.2.2" num-traits = "0.2.8" serde = { version = "1.0.104", features = ["derive"] } -sp-version = { version = "2.0.0-alpha.2", path = "../../../primitives/version" } -sp-io = { version = "2.0.0-alpha.2", path = "../../../primitives/io" } -sp-inherents = { version = "2.0.0-alpha.2", path = "../../../primitives/inherents" } -sp-timestamp = { version = "2.0.0-alpha.2", path = "../../../primitives/timestamp" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "../../telemetry" } -sc-keystore = { version = "2.0.0-alpha.2", path = "../../keystore" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../../api" } -sc-client = { version = "0.8.0-alpha.2", path = "../../" } -sc-consensus-epochs = { version = "0.8.0-alpha.2", path = "../epochs" } -sp-api = { version = "2.0.0-alpha.2", path = "../../../primitives/api" } -sp-block-builder = { version = "2.0.0-alpha.2", path = "../../../primitives/block-builder" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } -sc-consensus-uncles = { version = "0.8.0-alpha.2", path = "../uncles" } -sc-consensus-slots = { version = "0.8.0-alpha.2", path = "../slots" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -fork-tree = { version = "2.0.0-alpha.2", path = "../../../utils/fork-tree" } -futures = "0.3.1" +sp-version = { version = "2.0.0-alpha.5", path = "../../../primitives/version" } +sp-io = { version = "2.0.0-alpha.5", path = "../../../primitives/io" } +sp-inherents = { version = "2.0.0-alpha.5", path = "../../../primitives/inherents" } +sp-timestamp = { version = "2.0.0-alpha.5", path = "../../../primitives/timestamp" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../../telemetry" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../../keystore" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../api" } +sc-client = { version = "0.8.0-alpha.5", path = "../../" } +sc-consensus-epochs = { version = "0.8.0-alpha.5", path = "../epochs" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../primitives/api" } +sp-block-builder = { version = "2.0.0-alpha.5", path = "../../../primitives/block-builder" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } +sp-consensus-vrf = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/vrf" } +sc-consensus-uncles = { version = "0.8.0-alpha.5", path = "../uncles" } +sc-consensus-slots = { version = "0.8.0-alpha.5", path = "../slots" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +fork-tree = { version = "2.0.0-alpha.5", path = "../../../utils/fork-tree" } +futures = "0.3.4" futures-timer = "3.0.1" parking_lot = "0.10.0" log = "0.4.8" -schnorrkel = { version = "0.8.5", features = ["preaudit_deprecated"] } +schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } rand = "0.7.2" -merlin = "1.2.1" +merlin = "2.0" pdqselect = "0.1.0" derive_more = "0.99.2" [dev-dependencies] -sp-keyring = { version = "2.0.0-alpha.2", path = "../../../primitives/keyring" } -sc-executor = { version = "0.8.0-alpha.2", path = "../../executor" } -sc-network = { version = "0.8.0-alpha.2", path = "../../network" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../../../primitives/keyring" } +sc-executor = { version = "0.8.0-alpha.5", path = "../../executor" } +sc-network = { version = "0.8.0-alpha.5", path = "../../network" } sc-network-test = { version = "0.8.0-dev", path = "../../network/test" } -sc-service = { version = "0.8.0-alpha.2", path = "../../service" } +sc-service = { version = "0.8.0-alpha.5", path = "../../service" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../../test-utils/runtime/client" } -sc-block-builder = { version = "0.8.0-alpha.2", path = "../../block-builder" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "../../block-builder" } env_logger = "0.7.0" tempfile = "3.1.0" [features] test-helpers = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index 3f2cc4f0bcbce8913ca9183815edd5cffe39928f..6bdaeb5ddf0709a336b0bccf6d30698ba810d2dd 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-babe-rpc" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "RPC extensions for the BABE consensus algorithm" edition = "2018" @@ -9,24 +9,27 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sc-consensus-babe = { version = "0.8.0-alpha.2", path = "../" } +sc-consensus-babe = { version = "0.8.0-alpha.5", path = "../" } jsonrpc-core = "14.0.3" -jsonrpc-core-client = "14.0.3" +jsonrpc-core-client = "14.0.5" jsonrpc-derive = "14.0.3" -sp-consensus-babe = { version = "0.8.0-alpha.2", path = "../../../../primitives/consensus/babe" } +sp-consensus-babe = { version = "0.8.0-alpha.5", path = "../../../../primitives/consensus/babe" } serde = { version = "1.0.104", features=["derive"] } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../../primitives/blockchain" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../../primitives/runtime" } -sc-consensus-epochs = { version = "0.8.0-alpha.2", path = "../../epochs" } -futures = "0.3.1" +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../../primitives/blockchain" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../../primitives/runtime" } +sc-consensus-epochs = { version = "0.8.0-alpha.5", path = "../../epochs" } +futures = "0.3.4" derive_more = "0.99.2" -sp-api = { version = "2.0.0-alpha.2", path = "../../../../primitives/api" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../../primitives/consensus/common" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../../primitives/core" } -sc-keystore = { version = "2.0.0-alpha.2", path = "../../../keystore" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../../primitives/api" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../../primitives/consensus/common" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../../primitives/core" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../../../keystore" } [dev-dependencies] substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../../../test-utils/runtime/client" } -sp-application-crypto = { version = "2.0.0-alpha.2", path = "../../../../primitives/application-crypto" } -sp-keyring = { version = "2.0.0-alpha.2", path = "../../../../primitives/keyring" } +sp-application-crypto = { version = "2.0.0-alpha.5", path = "../../../../primitives/application-crypto" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../../../../primitives/keyring" } tempfile = "3.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/consensus/babe/src/authorship.rs b/client/consensus/babe/src/authorship.rs index a01ea63bbe111ef193d65de6a4264d7eb8a5ede4..074e582bff252395a18e691211eda944b380c723 100644 --- a/client/consensus/babe/src/authorship.rs +++ b/client/consensus/babe/src/authorship.rs @@ -21,7 +21,8 @@ use sp_consensus_babe::{ AuthorityId, BabeAuthorityWeight, BABE_ENGINE_ID, BABE_VRF_PREFIX, SlotNumber, AuthorityPair, BabeConfiguration }; -use sp_consensus_babe::digests::PreDigest; +use sp_consensus_babe::digests::{PreDigest, PrimaryPreDigest, SecondaryPreDigest}; +use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; use sp_core::{U256, blake2_256}; use codec::Encode; use schnorrkel::vrf::VRFInOut; @@ -127,10 +128,10 @@ fn claim_secondary_slot( }) { if pair.public() == *expected_author { - let pre_digest = PreDigest::Secondary { + let pre_digest = PreDigest::Secondary(SecondaryPreDigest { slot_number, authority_index: authority_index as u32, - }; + }); return Some((pre_digest, pair)); } @@ -199,12 +200,12 @@ fn claim_primary_slot( let pre_digest = get_keypair(&pair) .vrf_sign_after_check(transcript, |inout| super::authorship::check_primary_threshold(inout, threshold)) .map(|s| { - PreDigest::Primary { + PreDigest::Primary(PrimaryPreDigest { slot_number, - vrf_output: s.0.to_output(), - vrf_proof: s.1, + vrf_output: VRFOutput(s.0.to_output()), + vrf_proof: VRFProof(s.1), authority_index: authority_index as u32, - } + }) }); // early exit on first successful claim diff --git a/client/consensus/babe/src/aux_schema.rs b/client/consensus/babe/src/aux_schema.rs index 6f69e65940c05d60bfef77e839af4887c8910347..3ca2f560d43c7f1d309e70df47b40cc2e6f156da 100644 --- a/client/consensus/babe/src/aux_schema.rs +++ b/client/consensus/babe/src/aux_schema.rs @@ -25,7 +25,7 @@ use sc_client_api::backend::AuxStore; use sp_blockchain::{Result as ClientResult, Error as ClientError}; use sp_runtime::traits::Block as BlockT; use sp_consensus_babe::BabeBlockWeight; -use sc_consensus_epochs::{EpochChangesFor, SharedEpochChanges, migration::EpochChangesForV0}; +use sc_consensus_epochs::{EpochChangesFor, SharedEpochChanges}; use crate::Epoch; const BABE_EPOCH_CHANGES_VERSION: &[u8] = b"babe_epoch_changes_version"; @@ -57,11 +57,7 @@ pub(crate) fn load_epoch_changes( 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, - )?.map(|v0| v0.migrate()), - Some(BABE_EPOCH_CHANGES_CURRENT_VERSION) => load_decode::<_, EpochChangesFor>( + None | Some(BABE_EPOCH_CHANGES_CURRENT_VERSION) => load_decode::<_, EpochChangesFor>( backend, BABE_EPOCH_CHANGES_KEY, )?, @@ -127,72 +123,3 @@ pub(crate) fn load_block_weight( ) -> ClientResult> { load_decode(backend, block_weight_key(block_hash).as_slice()) } - -#[cfg(test)] -mod test { - use super::*; - use crate::Epoch; - use fork_tree::ForkTree; - use substrate_test_runtime_client; - use sp_core::H256; - use sp_runtime::traits::NumberFor; - use sc_consensus_epochs::{PersistedEpoch, PersistedEpochHeader, EpochHeader}; - use sp_consensus::Error as ConsensusError; - use sc_network_test::Block as TestBlock; - - #[test] - fn load_decode_from_v0_epoch_changes() { - let epoch = Epoch { - start_slot: 0, - authorities: vec![], - randomness: [0; 32], - epoch_index: 1, - duration: 100, - }; - let client = substrate_test_runtime_client::new(); - let mut v0_tree = ForkTree::, _>::new(); - v0_tree.import::<_, ConsensusError>( - Default::default(), - Default::default(), - PersistedEpoch::Regular(epoch), - &|_, _| Ok(false), // Test is single item only so this can be set to false. - ).unwrap(); - - client.insert_aux( - &[(BABE_EPOCH_CHANGES_KEY, - &EpochChangesForV0::::from_raw(v0_tree).encode()[..])], - &[], - ).unwrap(); - - assert_eq!( - load_decode::<_, u32>(&client, BABE_EPOCH_CHANGES_VERSION).unwrap(), - None, - ); - - let epoch_changes = load_epoch_changes::(&client).unwrap(); - - assert!( - epoch_changes.lock() - .tree() - .iter() - .map(|(_, _, epoch)| epoch.clone()) - .collect::>() == - vec![PersistedEpochHeader::Regular(EpochHeader { - start_slot: 0, - end_slot: 100, - })], - ); // PersistedEpochHeader does not implement Debug, so we use assert! directly. - - write_epoch_changes::( - &epoch_changes.lock(), - |values| { - client.insert_aux(values, &[]).unwrap(); - }, - ); - - assert_eq!( - load_decode::<_, u32>(&client, BABE_EPOCH_CHANGES_VERSION).unwrap(), - Some(1), - ); - } -} diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index f4373da47f26cca9145e93316a1c6692555cf719..5365aae2aa6cb30793a41ec9d4f7a02d3df6bc9a 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -62,7 +62,9 @@ pub use sp_consensus_babe::{ BabeApi, ConsensusLog, BABE_ENGINE_ID, SlotNumber, BabeConfiguration, AuthorityId, AuthorityPair, AuthoritySignature, BabeAuthorityWeight, VRF_OUTPUT_LENGTH, - digests::{PreDigest, CompatibleDigestItem, NextEpochDescriptor}, + digests::{ + CompatibleDigestItem, NextEpochDescriptor, PreDigest, PrimaryPreDigest, SecondaryPreDigest, + }, }; pub use sp_consensus::SyncOracle; use std::{ @@ -366,7 +368,7 @@ pub fn start_babe(BabeParams { &inherent_data_providers, )?; - babe_info!("Starting BABE Authorship worker"); + babe_info!("👶 Starting BABE Authorship worker"); Ok(sc_consensus_slots::start_slot_worker( config.0, select_chain, @@ -580,10 +582,10 @@ fn find_pre_digest(header: &B::Header) -> Result> // genesis block doesn't contain a pre digest so let's generate a // dummy one to not break any invariants in the rest of the code if header.number().is_zero() { - return Ok(PreDigest::Secondary { + return Ok(PreDigest::Secondary(SecondaryPreDigest { slot_number: 0, authority_index: 0, - }); + })); } let mut pre_digest: Option<_> = None; @@ -1008,7 +1010,7 @@ impl BlockImport for BabeBlockImport::FetchEpoch(parent_hash).into()) })?; - babe_info!("New epoch {} launching at block {} (block slot {} >= start slot {}).", + babe_info!("👶 New epoch {} launching at block {} (block slot {} >= start slot {}).", viable_epoch.as_ref().epoch_index, hash, slot_number, @@ -1016,7 +1018,7 @@ impl BlockImport for BabeBlockImport( let pre_digest = sp_runtime::generic::Digest { logs: vec![ Item::babe_pre_digest( - PreDigest::Secondary { + PreDigest::Secondary(SecondaryPreDigest { authority_index: 0, slot_number, - }, + }), ), ], }; diff --git a/client/consensus/babe/src/verification.rs b/client/consensus/babe/src/verification.rs index 70418b8aea1e38c4ac890ded5de1f64674b389df..2fd37280b3b369bdb1e73895dc9ae683030eaa23 100644 --- a/client/consensus/babe/src/verification.rs +++ b/client/consensus/babe/src/verification.rs @@ -15,11 +15,12 @@ // along with Substrate. If not, see . //! Verification for BABE headers. -use schnorrkel::vrf::{VRFOutput, VRFProof}; use sp_runtime::{traits::Header, traits::DigestItemFor}; use sp_core::{Pair, Public}; -use sp_consensus_babe::{AuthoritySignature, SlotNumber, AuthorityIndex, AuthorityPair, AuthorityId}; -use sp_consensus_babe::digests::{PreDigest, CompatibleDigestItem}; +use sp_consensus_babe::{AuthoritySignature, SlotNumber, AuthorityPair, AuthorityId}; +use sp_consensus_babe::digests::{ + PreDigest, PrimaryPreDigest, SecondaryPreDigest, CompatibleDigestItem +}; use sc_consensus_slots::CheckedHeader; use log::{debug, trace}; use super::{find_pre_digest, babe_err, Epoch, BlockT, Error}; @@ -93,27 +94,23 @@ pub(super) fn check_header( }; match &pre_digest { - PreDigest::Primary { vrf_output, vrf_proof, authority_index, slot_number } => { + PreDigest::Primary(primary) => { debug!(target: "babe", "Verifying Primary block"); - let digest = (vrf_output, vrf_proof, *authority_index, *slot_number); - check_primary_header::( pre_hash, - digest, + primary, sig, &epoch, config.c, )?; }, - PreDigest::Secondary { authority_index, slot_number } if config.secondary_slots => { + PreDigest::Secondary(secondary) if config.secondary_slots => { debug!(target: "babe", "Verifying Secondary block"); - let digest = (*authority_index, *slot_number); - check_secondary_header::( pre_hash, - digest, + secondary, sig, &epoch, )?; @@ -143,25 +140,23 @@ pub(super) struct VerifiedHeaderInfo { /// its parent since it is a primary block. fn check_primary_header( pre_hash: B::Hash, - pre_digest: (&VRFOutput, &VRFProof, AuthorityIndex, SlotNumber), + pre_digest: &PrimaryPreDigest, signature: AuthoritySignature, epoch: &Epoch, c: (u64, u64), ) -> Result<(), Error> { - let (vrf_output, vrf_proof, authority_index, slot_number) = pre_digest; - - let author = &epoch.authorities[authority_index as usize].0; + let author = &epoch.authorities[pre_digest.authority_index as usize].0; if AuthorityPair::verify(&signature, pre_hash, &author) { let (inout, _) = { let transcript = make_transcript( &epoch.randomness, - slot_number, + pre_digest.slot_number, epoch.epoch_index, ); schnorrkel::PublicKey::from_bytes(author.as_slice()).and_then(|p| { - p.vrf_verify(transcript, vrf_output, vrf_proof) + p.vrf_verify(transcript, &pre_digest.vrf_output, &pre_digest.vrf_proof) }).map_err(|s| { babe_err(Error::VRFVerificationFailed(s)) })? @@ -170,7 +165,7 @@ fn check_primary_header( let threshold = calculate_primary_threshold( c, &epoch.authorities, - authority_index as usize, + pre_digest.authority_index as usize, ); if !check_primary_threshold(&inout, threshold) { @@ -189,21 +184,19 @@ fn check_primary_header( /// compared to its parent since it is a secondary block. fn check_secondary_header( pre_hash: B::Hash, - pre_digest: (AuthorityIndex, SlotNumber), + pre_digest: &SecondaryPreDigest, signature: AuthoritySignature, epoch: &Epoch, ) -> Result<(), Error> { - let (authority_index, slot_number) = pre_digest; - // check the signature is valid under the expected authority and // chain state. let expected_author = secondary_slot_author( - slot_number, + pre_digest.slot_number, &epoch.authorities, epoch.randomness, ).ok_or_else(|| Error::NoSecondaryAuthorExpected)?; - let author = &epoch.authorities[authority_index as usize].0; + let author = &epoch.authorities[pre_digest.authority_index as usize].0; if expected_author != author { return Err(Error::InvalidAuthor(expected_author.clone(), author.clone())); diff --git a/client/consensus/epochs/Cargo.toml b/client/consensus/epochs/Cargo.toml index fe2365429892121a4092a1f63021725af6cf5028..3496141ec712bdc4c8daa043e10faaecb13d8b7e 100644 --- a/client/consensus/epochs/Cargo.toml +++ b/client/consensus/epochs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-epochs" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Generic epochs-based utilities for consensus" edition = "2018" @@ -9,9 +9,12 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } parking_lot = "0.10.0" -fork-tree = { version = "2.0.0-alpha.2", path = "../../../utils/fork-tree" } -sp-runtime = { path = "../../../primitives/runtime" , version = "2.0.0-alpha.2"} -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -sc-client-api = { path = "../../api" , version = "2.0.0-alpha.2"} +fork-tree = { version = "2.0.0-alpha.5", path = "../../../utils/fork-tree" } +sp-runtime = { path = "../../../primitives/runtime" , version = "2.0.0-alpha.5"} +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +sc-client-api = { path = "../../api" , version = "2.0.0-alpha.5"} + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/consensus/epochs/src/lib.rs b/client/consensus/epochs/src/lib.rs index 001c172b3490573dea6a0dcfbefc428cf79c13aa..d5816d960cc9eaaa0dabd16ee11899ac220d84d5 100644 --- a/client/consensus/epochs/src/lib.rs +++ b/client/consensus/epochs/src/lib.rs @@ -16,8 +16,6 @@ //! Generic utilities for epoch-based consensus engines. -pub mod migration; - use std::{sync::Arc, ops::Add, collections::BTreeMap, borrow::{Borrow, BorrowMut}}; use parking_lot::Mutex; use codec::{Encode, Decode}; diff --git a/client/consensus/epochs/src/migration.rs b/client/consensus/epochs/src/migration.rs deleted file mode 100644 index e4717b5584e0ec67801ce3d264e90ff350286deb..0000000000000000000000000000000000000000 --- a/client/consensus/epochs/src/migration.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019-2020 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 . - -//! Migration types for epoch changes. - -use std::collections::BTreeMap; -use codec::{Encode, Decode}; -use fork_tree::ForkTree; -use sp_runtime::traits::{Block as BlockT, NumberFor}; -use crate::{Epoch, EpochChanges, PersistedEpoch, PersistedEpochHeader}; - -/// Legacy definition of epoch changes. -#[derive(Clone, Encode, Decode)] -pub struct EpochChangesV0 { - inner: ForkTree>, -} - -/// Type alias for legacy definition of epoch changes. -pub type EpochChangesForV0 = EpochChangesV0<::Hash, NumberFor, Epoch>; - -impl EpochChangesV0 where - Hash: PartialEq + Ord + Copy, - Number: Ord + Copy, -{ - /// Create a new value of this type from raw. - pub fn from_raw(inner: ForkTree>) -> Self { - Self { inner } - } - - /// Migrate the type into current epoch changes definition. - pub fn migrate(self) -> EpochChanges { - let mut epochs = BTreeMap::new(); - - let inner = self.inner.map(&mut |hash, number, data| { - let header = PersistedEpochHeader::from(&data); - epochs.insert((*hash, *number), data); - header - }); - - EpochChanges { inner, epochs } - } -} diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index 11fee0e3f9e608c73e716f5ac8a84480140e31d1..b7e5f7b0b1a9ff7c3f14303fa8c0065aa572048d 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-manual-seal" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Manual sealing engine for Substrate" edition = "2018" @@ -10,27 +10,31 @@ repository = "https://github.com/paritytech/substrate/" [dependencies] derive_more = "0.99.2" -futures = "0.3.1" +futures = "0.3.4" jsonrpc-core = "14.0.5" jsonrpc-core-client = "14.0.5" jsonrpc-derive = "14.0.5" log = "0.4.8" parking_lot = "0.10.0" serde = { version = "1.0", features=["derive"] } +assert_matches = "1.3.0" -sc-client = { path = "../../../client" , version = "0.8.0-alpha.2"} -sc-client-api = { path = "../../../client/api" , version = "2.0.0-alpha.2"} -sc-transaction-pool = { path = "../../transaction-pool" , version = "2.0.0-alpha.2"} -sp-blockchain = { path = "../../../primitives/blockchain" , version = "2.0.0-alpha.2"} -sp-consensus = { package = "sp-consensus", path = "../../../primitives/consensus/common" , version = "0.8.0-alpha.2"} -sp-inherents = { path = "../../../primitives/inherents" , version = "2.0.0-alpha.2"} -sp-runtime = { path = "../../../primitives/runtime" , version = "2.0.0-alpha.2"} -sp-transaction-pool = { path = "../../../primitives/transaction-pool" , version = "2.0.0-alpha.2"} +sc-client = { path = "../../../client" , version = "0.8.0-alpha.5"} +sc-client-api = { path = "../../../client/api" , version = "2.0.0-alpha.5"} +sc-transaction-pool = { path = "../../transaction-pool" , version = "2.0.0-alpha.5"} +sp-blockchain = { path = "../../../primitives/blockchain" , version = "2.0.0-alpha.5"} +sp-consensus = { package = "sp-consensus", path = "../../../primitives/consensus/common" , version = "0.8.0-alpha.5"} +sp-inherents = { path = "../../../primitives/inherents" , version = "2.0.0-alpha.5"} +sp-runtime = { path = "../../../primitives/runtime" , version = "2.0.0-alpha.5"} +sp-transaction-pool = { path = "../../../primitives/transaction-pool" , version = "2.0.0-alpha.5"} [dev-dependencies] -sc-basic-authorship = { path = "../../basic-authorship" , version = "0.8.0-alpha.2"} +sc-basic-authorship = { path = "../../basic-authorship" , version = "0.8.0-alpha.5"} substrate-test-runtime-client = { path = "../../../test-utils/runtime/client" , version = "2.0.0-dev"} substrate-test-runtime-transaction-pool = { path = "../../../test-utils/runtime/transaction-pool" , version = "2.0.0-dev"} tokio = { version = "0.2", features = ["rt-core", "macros"] } env_logger = "0.7.0" tempfile = "3.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/consensus/manual-seal/src/finalize_block.rs b/client/consensus/manual-seal/src/finalize_block.rs index 419159709f2db84ae51cba0fb861458123b63332..b3b60e223805b857c23602ae718a2347c8cb3291 100644 --- a/client/consensus/manual-seal/src/finalize_block.rs +++ b/client/consensus/manual-seal/src/finalize_block.rs @@ -57,8 +57,8 @@ pub async fn finalize_block(params: FinalizeBlockParams) rpc::send_result(&mut sender, Err(e.into())) } Ok(()) => { - log::info!("Successfully finalized block: {}", hash); + log::info!("✅ Successfully finalized block: {}", hash); rpc::send_result(&mut sender, Ok(())) } } -} \ No newline at end of file +} diff --git a/client/consensus/manual-seal/src/lib.rs b/client/consensus/manual-seal/src/lib.rs index 18dc91ad34d625fd6b7ce661bea7a56aeebdcc38..f3a0ca887fd005ed34ade733ed13d9e8e68898b9 100644 --- a/client/consensus/manual-seal/src/lib.rs +++ b/client/consensus/manual-seal/src/lib.rs @@ -224,7 +224,7 @@ mod tests { txpool::Options, }; use substrate_test_runtime_transaction_pool::{TestApi, uxt}; - use sp_transaction_pool::TransactionPool; + use sp_transaction_pool::{TransactionPool, MaintainedTransactionPool, TransactionSource}; use sp_runtime::generic::BlockId; use sp_blockchain::HeaderBackend; use sp_consensus::ImportedAux; @@ -236,6 +236,8 @@ mod tests { Arc::new(TestApi::empty()) } + const SOURCE: TransactionSource = TransactionSource::External; + #[tokio::test] async fn instant_seal() { let builder = TestClientBuilder::new(); @@ -278,7 +280,7 @@ mod tests { rt.block_on(future); }); // submit a transaction to pool. - let result = pool.submit_one(&BlockId::Number(0), uxt(Alice, 0)).await; + let result = pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Alice, 0)).await; // assert that it was successfully imported assert!(result.is_ok()); // assert that the background task returns ok @@ -330,7 +332,7 @@ mod tests { rt.block_on(future); }); // submit a transaction to pool. - let result = pool.submit_one(&BlockId::Number(0), uxt(Alice, 0)).await; + let result = pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Alice, 0)).await; // assert that it was successfully imported assert!(result.is_ok()); let (tx, rx) = futures::channel::oneshot::channel(); @@ -399,7 +401,7 @@ mod tests { rt.block_on(future); }); // submit a transaction to pool. - let result = pool.submit_one(&BlockId::Number(0), uxt(Alice, 0)).await; + let result = pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Alice, 0)).await; // assert that it was successfully imported assert!(result.is_ok()); @@ -430,20 +432,30 @@ mod tests { ); // assert that there's a new block in the db. assert!(backend.blockchain().header(BlockId::Number(0)).unwrap().is_some()); - assert!(pool.submit_one(&BlockId::Number(1), uxt(Alice, 1)).await.is_ok()); + assert!(pool.submit_one(&BlockId::Number(1), SOURCE, uxt(Alice, 1)).await.is_ok()); + + pool.maintain(sp_transaction_pool::ChainEvent::NewBlock { + id: BlockId::Number(1), + header: backend.blockchain().header(BlockId::Number(1)).expect("db error").expect("imported above"), + is_new_best: true, + retracted: vec![], + }).await; let (tx1, rx1) = futures::channel::oneshot::channel(); assert!(sink.send(EngineCommand::SealNewBlock { - parent_hash: Some(created_block.hash.clone()), + parent_hash: Some(created_block.hash), sender: Some(tx1), create_empty: false, finalize: false, }).await.is_ok()); - assert!(rx1.await.unwrap().is_ok()); + assert_matches::assert_matches!( + rx1.await.expect("should be no error receiving"), + Ok(_) + ); assert!(backend.blockchain().header(BlockId::Number(1)).unwrap().is_some()); pool_api.increment_nonce(Alice.into()); - assert!(pool.submit_one(&BlockId::Number(2), uxt(Alice, 2)).await.is_ok()); + assert!(pool.submit_one(&BlockId::Number(2), SOURCE, uxt(Alice, 2)).await.is_ok()); let (tx2, rx2) = futures::channel::oneshot::channel(); assert!(sink.send(EngineCommand::SealNewBlock { parent_hash: Some(created_block.hash), diff --git a/client/consensus/pow/Cargo.toml b/client/consensus/pow/Cargo.toml index f95d196b62f7a94fec013416252456e50395b02f..c7832baae0ee4a2a27a35b88c4ec34f31e0854e5 100644 --- a/client/consensus/pow/Cargo.toml +++ b/client/consensus/pow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-pow" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "PoW consensus algorithm for substrate" edition = "2018" @@ -9,17 +9,20 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-api = { version = "2.0.0-alpha.2", path = "../../../primitives/api" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../../api" } -sp-block-builder = { version = "2.0.0-alpha.2", path = "../../../primitives/block-builder" } -sp-inherents = { version = "2.0.0-alpha.2", path = "../../../primitives/inherents" } -sp-consensus-pow = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/pow" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../primitives/api" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../api" } +sp-block-builder = { version = "2.0.0-alpha.5", path = "../../../primitives/block-builder" } +sp-inherents = { version = "2.0.0-alpha.5", path = "../../../primitives/inherents" } +sp-consensus-pow = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/pow" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } log = "0.4.8" futures = { version = "0.3.1", features = ["compat"] } -sp-timestamp = { version = "2.0.0-alpha.2", path = "../../../primitives/timestamp" } +sp-timestamp = { version = "2.0.0-alpha.5", path = "../../../primitives/timestamp" } derive_more = "0.99.2" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index 49d2e64f6050c92b8603e6a3cf034e19263085a1..de41ea7bd2356f02f5857344b70eae335273a540 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -151,7 +151,7 @@ pub trait PowAlgorithm { /// /// This function will be called twice during the import process, so the implementation /// should be properly cached. - fn difficulty(&self, parent: &BlockId) -> Result>; + fn difficulty(&self, parent: B::Hash) -> Result>; /// Verify that the seal is valid against given pre hash when parent block is not yet imported. /// /// None means that preliminary verify is not available for this algorithm. @@ -335,7 +335,7 @@ impl BlockImport for PowBlockImport difficulty, - None => self.algorithm.difficulty(&BlockId::hash(parent_hash))?, + None => self.algorithm.difficulty(parent_hash)?, }; let pre_hash = block.header.hash(); @@ -617,9 +617,7 @@ fn mine_loop( let (header, body) = proposal.block.deconstruct(); let (difficulty, seal) = { - let difficulty = algorithm.difficulty( - &BlockId::Hash(best_hash), - )?; + let difficulty = algorithm.difficulty(best_hash)?; loop { let seal = algorithm.mine( diff --git a/client/consensus/slots/Cargo.toml b/client/consensus/slots/Cargo.toml index fe7958b257203c9bf24e0582515e249d3a34a74c..bf973ef47a893bbd45a6bb47b03a9f5afbfd99df 100644 --- a/client/consensus/slots/Cargo.toml +++ b/client/consensus/slots/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-slots" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Generic slots-based utilities for consensus" edition = "2018" @@ -10,20 +10,23 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../../api" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../../primitives/state-machine" } -sp-api = { version = "2.0.0-alpha.2", path = "../../../primitives/api" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "../../telemetry" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } -sp-inherents = { version = "2.0.0-alpha.2", path = "../../../primitives/inherents" } -futures = "0.3.1" +codec = { package = "parity-scale-codec", version = "1.3.0" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../api" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../../primitives/state-machine" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../primitives/api" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../../telemetry" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } +sp-inherents = { version = "2.0.0-alpha.5", path = "../../../primitives/inherents" } +futures = "0.3.4" futures-timer = "3.0.1" parking_lot = "0.10.0" log = "0.4.8" [dev-dependencies] substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../../test-utils/runtime/client" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/consensus/slots/src/lib.rs b/client/consensus/slots/src/lib.rs index 7fc4bdc7d27e483f8586e16150b348a59153d692..d0f1f6ec4bf9ea1f91cbe48974981df92d3ac918 100644 --- a/client/consensus/slots/src/lib.rs +++ b/client/consensus/slots/src/lib.rs @@ -258,10 +258,10 @@ pub trait SimpleSlotWorker { Box::new(futures::future::select(proposing, delay).map(move |v| match v { futures::future::Either::Left((b, _)) => b.map(|b| (b, claim)), futures::future::Either::Right(_) => { - info!("Discarding proposal for slot {}; block production took too long", slot_number); + info!("⌛️ Discarding proposal for slot {}; block production took too long", slot_number); // If the node was compiled with debug, tell the user to use release optimizations. #[cfg(build_type="debug")] - info!("Recompile your node in `--release` mode to mitigate this problem."); + info!("👉 Recompile your node in `--release` mode to mitigate this problem."); telemetry!(CONSENSUS_INFO; "slots.discarding_proposal_took_too_long"; "slot" => slot_number, ); @@ -289,7 +289,7 @@ pub trait SimpleSlotWorker { ); info!( - "Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.", + "🔖 Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.", header_num, block_import_params.post_hash(), header_hash, diff --git a/client/consensus/uncles/Cargo.toml b/client/consensus/uncles/Cargo.toml index ad325ed79f7a576bd957fd1a06fd5e473eb60394..7e8014199baa5241935cc198cce100bba9677cfa 100644 --- a/client/consensus/uncles/Cargo.toml +++ b/client/consensus/uncles/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-uncles" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Generic uncle inclusion utilities for consensus" edition = "2018" @@ -9,10 +9,13 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sc-client-api = { version = "2.0.0-alpha.2", path = "../../api" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-authorship = { version = "2.0.0-alpha.2", path = "../../../primitives/authorship" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } -sp-inherents = { version = "2.0.0-alpha.2", path = "../../../primitives/inherents" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../api" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-authorship = { version = "2.0.0-alpha.5", path = "../../../primitives/authorship" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } +sp-inherents = { version = "2.0.0-alpha.5", path = "../../../primitives/inherents" } log = "0.4.8" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index d30b6f95e4253f6abc9447229a9099e08ef4a735..9308d4ee74adf169deb8faff092fd81f26cfbdd9 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-client-db" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,34 +12,37 @@ description = "Client backend that uses RocksDB database as storage." parking_lot = "0.10.0" log = "0.4.8" rand = "0.7" -kvdb = "0.4.0" -kvdb-rocksdb = { version = "0.6", optional = true } -kvdb-memorydb = "0.4.0" +kvdb = "0.5.0" +kvdb-rocksdb = { version = "0.7", optional = true } +kvdb-memorydb = "0.5.0" linked-hash-map = "0.5.2" hash-db = "0.15.2" -parity-util-mem = { version = "0.5.2", default-features = false, features = ["std"] } -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } +parity-util-mem = { version = "0.6.0", default-features = false, features = ["std"] } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sc-client = { version = "0.8.0-alpha.2", path = "../" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../primitives/state-machine" } -sc-executor = { version = "0.8.0-alpha.2", path = "../executor" } -sc-state-db = { version = "0.8.0-alpha.2", path = "../state-db" } -sp-trie = { version = "2.0.0-alpha.2", path = "../../primitives/trie" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../primitives/consensus/common" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.8.0-alpha.2", path = "../../utils/prometheus" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sc-client = { version = "0.8.0-alpha.5", path = "../" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../primitives/state-machine" } +sc-executor = { version = "0.8.0-alpha.5", path = "../executor" } +sc-state-db = { version = "0.8.0-alpha.5", path = "../state-db" } +sp-trie = { version = "2.0.0-alpha.5", path = "../../primitives/trie" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/common" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.8.0-alpha.5", path = "../../utils/prometheus" } [dev-dependencies] -sp-keyring = { version = "2.0.0-alpha.2", path = "../../primitives/keyring" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../../primitives/keyring" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client" } env_logger = "0.7.0" quickcheck = "0.9" -kvdb-rocksdb = "0.6" +kvdb-rocksdb = "0.7" tempfile = "3" [features] default = [] test-helpers = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index 1353bc57307b9c5d664b005f7d1bcbb997ca093f..cae0d56d8ed5eea8fef0e77c92eec6c2d5d9a61b 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-executor" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,22 +11,22 @@ documentation = "https://docs.rs/sc-executor" [dependencies] derive_more = "0.99.2" -codec = { package = "parity-scale-codec", version = "1.2.0" } -sp-io = { version = "2.0.0-alpha.2", path = "../../primitives/io" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-trie = { version = "2.0.0-alpha.2", path = "../../primitives/trie" } -sp-serializer = { version = "2.0.0-alpha.2", path = "../../primitives/serializer" } -sp-version = { version = "2.0.0-alpha.2", path = "../../primitives/version" } -sp-panic-handler = { version = "2.0.0-alpha.2", path = "../../primitives/panic-handler" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +sp-io = { version = "2.0.0-alpha.5", path = "../../primitives/io" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-trie = { version = "2.0.0-alpha.5", path = "../../primitives/trie" } +sp-serializer = { version = "2.0.0-alpha.5", path = "../../primitives/serializer" } +sp-version = { version = "2.0.0-alpha.5", path = "../../primitives/version" } +sp-panic-handler = { version = "2.0.0-alpha.5", path = "../../primitives/panic-handler" } wasmi = "0.6.2" parity-wasm = "0.41.0" lazy_static = "1.4.0" -sp-wasm-interface = { version = "2.0.0-alpha.2", path = "../../primitives/wasm-interface" } -sp-runtime-interface = { version = "2.0.0-alpha.2", path = "../../primitives/runtime-interface" } -sp-externalities = { version = "0.8.0-alpha.2", path = "../../primitives/externalities" } -sc-executor-common = { version = "0.8.0-alpha.2", path = "common" } -sc-executor-wasmi = { version = "0.8.0-alpha.2", path = "wasmi" } -sc-executor-wasmtime = { version = "0.8.0-alpha.2", path = "wasmtime", optional = true } +sp-wasm-interface = { version = "2.0.0-alpha.5", path = "../../primitives/wasm-interface" } +sp-runtime-interface = { version = "2.0.0-alpha.5", path = "../../primitives/runtime-interface" } +sp-externalities = { version = "0.8.0-alpha.5", path = "../../primitives/externalities" } +sc-executor-common = { version = "0.8.0-alpha.5", path = "common" } +sc-executor-wasmi = { version = "0.8.0-alpha.5", path = "wasmi" } +sc-executor-wasmtime = { version = "0.8.0-alpha.5", path = "wasmtime", optional = true } parking_lot = "0.10.0" log = "0.4.8" libsecp256k1 = "0.3.4" @@ -37,9 +37,9 @@ wabt = "0.9.2" hex-literal = "0.2.1" sc-runtime-test = { version = "2.0.0-dev", path = "runtime-test" } substrate-test-runtime = { version = "2.0.0-dev", path = "../../test-utils/runtime" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../primitives/state-machine" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../primitives/state-machine" } test-case = "0.3.3" -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } [features] default = [ "std" ] @@ -52,3 +52,6 @@ wasmtime = [ wasmi-errno = [ "wasmi/errno" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 04db56938a4bc1105b140b8f1d74207a2c5d3395..1f52b959d00b1fc72d56b112267a4d70eb0d1b90 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-executor-common" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,13 +12,16 @@ documentation = "https://docs.rs/sc-executor-common/" [dependencies] log = "0.4.8" derive_more = "0.99.2" -codec = { package = "parity-scale-codec", version = "1.2.0" } +codec = { package = "parity-scale-codec", version = "1.3.0" } wasmi = "0.6.2" -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-allocator = { version = "2.0.0-alpha.2", path = "../../../primitives/allocator" } -sp-wasm-interface = { version = "2.0.0-alpha.2", path = "../../../primitives/wasm-interface" } -sp-runtime-interface = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime-interface" } -sp-serializer = { version = "2.0.0-alpha.2", path = "../../../primitives/serializer" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-allocator = { version = "2.0.0-alpha.5", path = "../../../primitives/allocator" } +sp-wasm-interface = { version = "2.0.0-alpha.5", path = "../../../primitives/wasm-interface" } +sp-runtime-interface = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime-interface" } +sp-serializer = { version = "2.0.0-alpha.5", path = "../../../primitives/serializer" } [features] default = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/executor/runtime-test/Cargo.toml b/client/executor/runtime-test/Cargo.toml index ad7c44718d243433ae6e2e775d29b213d091ca3a..e50061f4f24bb71639a5204cf326d6be7d397e6f 100644 --- a/client/executor/runtime-test/Cargo.toml +++ b/client/executor/runtime-test/Cargo.toml @@ -10,12 +10,12 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/io" } -sp-sandbox = { version = "0.8.0-alpha.2", default-features = false, path = "../../../primitives/sandbox" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/runtime" } -sp-allocator = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/allocator" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/io" } +sp-sandbox = { version = "0.8.0-alpha.5", default-features = false, path = "../../../primitives/sandbox" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/runtime" } +sp-allocator = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/allocator" } [build-dependencies] wasm-builder-runner = { version = "1.0.5", package = "substrate-wasm-builder-runner", path = "../../../utils/wasm-builder-runner" } @@ -28,3 +28,6 @@ std = [ "sp-std/std", "sp-allocator/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index 5641755ef18ba10b1312faba4ec3730343483935..778bc808004bb2470b528c802bd14c1d0fe1bc51 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -483,5 +483,7 @@ mod tests { 2, ); }); + + my_interface::say_hello_world("hey"); } } diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index aa259e29f8dfb3dfb564b3b8060af8f238f537e6..ea8637b9e2830972b19639440546cf486b606c5d 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -1,21 +1,24 @@ [package] name = "sc-executor-wasmi" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" description = "This crate provides an implementation of `WasmRuntime` that is baked by wasmi." -documentation = "https://docs.rs/sc-execturo-wasmi" +documentation = "https://docs.rs/sc-executor-wasmi" [dependencies] log = "0.4.8" wasmi = "0.6.2" parity-wasm = "0.41.0" -codec = { package = "parity-scale-codec", version = "1.2.0" } -sc-executor-common = { version = "0.8.0-alpha.2", path = "../common" } -sp-wasm-interface = { version = "2.0.0-alpha.2", path = "../../../primitives/wasm-interface" } -sp-runtime-interface = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime-interface" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-allocator = { version = "2.0.0-alpha.2", path = "../../../primitives/allocator" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +sc-executor-common = { version = "0.8.0-alpha.5", path = "../common" } +sp-wasm-interface = { version = "2.0.0-alpha.5", path = "../../../primitives/wasm-interface" } +sp-runtime-interface = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime-interface" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-allocator = { version = "2.0.0-alpha.5", path = "../../../primitives/allocator" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 9f8784cc981b7d1a79438846255d17afcf27d9d3..11f99e7876bbc57dae3b3ec792a3efe30161d476 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-executor-wasmtime" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,13 +12,16 @@ description = "Defines a `WasmRuntime` that uses the Wasmtime JIT to execute." log = "0.4.8" scoped-tls = "1.0" parity-wasm = "0.41.0" -codec = { package = "parity-scale-codec", version = "1.2.0" } -sc-executor-common = { version = "0.8.0-alpha.2", path = "../common" } -sp-wasm-interface = { version = "2.0.0-alpha.2", path = "../../../primitives/wasm-interface" } -sp-runtime-interface = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime-interface" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-allocator = { version = "2.0.0-alpha.2", path = "../../../primitives/allocator" } -wasmtime = { git = "https://github.com/paritytech/wasmtime", branch = "a-thread-safe-api" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +sc-executor-common = { version = "0.8.0-alpha.5", path = "../common" } +sp-wasm-interface = { version = "2.0.0-alpha.5", path = "../../../primitives/wasm-interface" } +sp-runtime-interface = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime-interface" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-allocator = { version = "2.0.0-alpha.5", path = "../../../primitives/allocator" } +wasmtime = { package = "substrate-wasmtime", version = "0.13.0-threadsafe.1" } [dev-dependencies] assert_matches = "1.3.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 6cee0cd8526f6dcbd2ed9019b172dc30b009ca05..6be790dc19c69d037ec7647959c500aa91bfa617 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-finality-grandpa" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,44 +11,47 @@ documentation = "https://docs.rs/sc-finality-grandpa" [dependencies] -fork-tree = { version = "2.0.0-alpha.2", path = "../../utils/fork-tree" } -futures = "0.3.1" +fork-tree = { version = "2.0.0-alpha.5", path = "../../utils/fork-tree" } +futures = "0.3.4" futures-timer = "3.0.1" log = "0.4.8" parking_lot = "0.10.0" rand = "0.7.2" assert_matches = "1.3.0" -parity-scale-codec = { version = "1.2.0", features = ["derive"] } -sp-arithmetic = { version = "2.0.0-alpha.2", path = "../../primitives/arithmetic" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sp-consensus = { version = "0.8.0-alpha.1", path = "../../primitives/consensus/common" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "../telemetry" } -sc-keystore = { version = "2.0.0-alpha.2", path = "../keystore" } +parity-scale-codec = { version = "1.3.0", features = ["derive"] } +sp-arithmetic = { version = "2.0.0-alpha.5", path = "../../primitives/arithmetic" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/common" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../telemetry" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../keystore" } serde_json = "1.0.41" -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sc-client = { version = "0.8.0-alpha.2", path = "../" } -sp-inherents = { version = "2.0.0-alpha.2", path = "../../primitives/inherents" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -sc-network = { version = "0.8.0-alpha.2", path = "../network" } -sc-network-gossip = { version = "0.8.0-alpha.2", path = "../network-gossip" } -sp-finality-tracker = { version = "2.0.0-alpha.2", path = "../../primitives/finality-tracker" } -sp-finality-grandpa = { version = "2.0.0-alpha.2", path = "../../primitives/finality-grandpa" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.8.0-alpha.2" } -sc-block-builder = { version = "0.8.0-alpha.2", path = "../block-builder" } -finality-grandpa = { version = "0.11.1", features = ["derive-codec"] } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sc-client = { version = "0.8.0-alpha.5", path = "../" } +sp-inherents = { version = "2.0.0-alpha.5", path = "../../primitives/inherents" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +sc-network = { version = "0.8.0-alpha.5", path = "../network" } +sc-network-gossip = { version = "0.8.0-alpha.5", path = "../network-gossip" } +sp-finality-tracker = { version = "2.0.0-alpha.5", path = "../../primitives/finality-tracker" } +sp-finality-grandpa = { version = "2.0.0-alpha.5", path = "../../primitives/finality-grandpa" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.8.0-alpha.5"} +sc-block-builder = { version = "0.8.0-alpha.5", path = "../block-builder" } +finality-grandpa = { version = "0.11.2", features = ["derive-codec"] } pin-project = "0.4.6" [dev-dependencies] -finality-grandpa = { version = "0.11.1", features = ["derive-codec", "test-helpers"] } -sc-network = { version = "0.8.0-alpha.2", path = "../network" } +finality-grandpa = { version = "0.11.2", features = ["derive-codec", "test-helpers"] } +sc-network = { version = "0.8.0-alpha.5", path = "../network" } sc-network-test = { version = "0.8.0-dev", path = "../network/test" } -sp-keyring = { version = "2.0.0-alpha.2", path = "../../primitives/keyring" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../../primitives/keyring" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client" } -sp-consensus-babe = { version = "0.8.0-alpha.2", path = "../../primitives/consensus/babe" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../primitives/state-machine" } +sp-consensus-babe = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/babe" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../primitives/state-machine" } env_logger = "0.7.0" tokio = { version = "0.2", features = ["rt-core"] } tempfile = "3.1.0" -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api" } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/finality-grandpa/src/communication/gossip.rs b/client/finality-grandpa/src/communication/gossip.rs index 89c507f39af2e52e7fc8766ddd86b2019d74b85f..6b245a4652a5f0833417ad13b3e6d566768bd882 100644 --- a/client/finality-grandpa/src/communication/gossip.rs +++ b/client/finality-grandpa/src/communication/gossip.rs @@ -722,7 +722,15 @@ impl Inner { last_commit: None, }), Some(ref mut v) => if v.set_id == set_id { - return None + if self.authorities != authorities { + debug!(target: "afg", + "Gossip validator noted set {:?} twice with different authorities. \ + Was the authority set hard forked?", + set_id, + ); + self.authorities = authorities; + } + return None; } else { v }, @@ -788,6 +796,7 @@ impl Inner { // ensure authority is part of the set. if !self.authorities.contains(&full.message.id) { + debug!(target: "afg", "Message from unknown voter: {}", full.message.id); telemetry!(CONSENSUS_DEBUG; "afg.bad_msg_signature"; "signature" => ?full.message.id); return Action::Discard(cost::UNKNOWN_VOTER); } @@ -1380,6 +1389,11 @@ impl GossipValidator { (action, broadcast_topics, peer_reply) } + + #[cfg(test)] + fn inner(&self) -> &parking_lot::RwLock> { + &self.inner + } } impl sc_network_gossip::Validator for GossipValidator { @@ -2555,4 +2569,19 @@ mod tests { &commit(0, 1, 2), )); } + + #[test] + fn allow_noting_different_authorities_for_same_set() { + let (val, _) = GossipValidator::::new(config(), voter_set_state(), None); + + let a1 = vec![AuthorityId::default()]; + val.note_set(SetId(1), a1.clone(), |_, _| {}); + + assert_eq!(val.inner().read().authorities, a1); + + let a2 = vec![AuthorityId::default(), AuthorityId::default()]; + 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 82f24ff05b3d5f028c6485d7f7daf02beb64f695..52bfdbc81881762077c6d0c61e88ea483f8091e8 100644 --- a/client/finality-grandpa/src/communication/mod.rs +++ b/client/finality-grandpa/src/communication/mod.rs @@ -451,8 +451,9 @@ impl> Future for NetworkBridge { } match self.gossip_engine.lock().poll_unpin(cx) { - // The gossip engine future finished. We should do the same. - Poll::Ready(()) => return Poll::Ready(Ok(())), + Poll::Ready(()) => return Poll::Ready( + Err(Error::Network("Gossip engine future finished.".into())) + ), Poll::Pending => {}, } @@ -474,12 +475,12 @@ fn incoming_global( gossip_validator: &Arc>, voters: &VoterSet, | { - let precommits_signed_by: Vec = - msg.message.auth_data.iter().map(move |(_, a)| { - format!("{}", a) - }).collect(); - if voters.len() <= TELEMETRY_VOTERS_LIMIT { + let precommits_signed_by: Vec = + msg.message.auth_data.iter().map(move |(_, a)| { + format!("{}", a) + }).collect(); + telemetry!(CONSENSUS_INFO; "afg.received_commit"; "contains_precommits_signed_by" => ?precommits_signed_by, "target_number" => ?msg.message.target_number.clone(), diff --git a/client/finality-grandpa/src/communication/tests.rs b/client/finality-grandpa/src/communication/tests.rs index f964cea469c3f3435e520e3370e8bdfb1c1e06ee..7c9170a48e5d1199a264e2697bad2372f15b7edc 100644 --- a/client/finality-grandpa/src/communication/tests.rs +++ b/client/finality-grandpa/src/communication/tests.rs @@ -357,7 +357,7 @@ fn good_commit_leads_to_relay() { #[test] fn bad_commit_leads_to_report() { - env_logger::init(); + let _ = env_logger::try_init(); let private = [Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let public = make_ids(&private[..]); let voter_set = Arc::new(public.iter().cloned().collect::>()); diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index a0f37f20cb390926cb6c43662e1370772863313b..dec9492482a5e78ba6e06a752ea29fa9d644acd0 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -636,6 +636,7 @@ where self.client.clone(), incoming, "round", + None, ).map_err(Into::into)); // schedule network message cleanup when sink drops. @@ -1090,9 +1091,9 @@ pub(crate) fn finalize_block( let (new_id, set_ref) = authority_set.current(); if set_ref.len() > 16 { - info!("Applying GRANDPA set change to new set with {} authorities", set_ref.len()); + info!("👴 Applying GRANDPA set change to new set with {} authorities", set_ref.len()); } else { - info!("Applying GRANDPA set change to new set {:?}", set_ref); + info!("👴 Applying GRANDPA set change to new set {:?}", set_ref); } telemetry!(CONSENSUS_INFO; "afg.generating_new_authority_set"; diff --git a/client/finality-grandpa/src/import.rs b/client/finality-grandpa/src/import.rs index ea1deccdafbf5d4a428947917b797779ff0957fe..004e14bcba498050a2f8015ecea11fa49e8010e3 100644 --- a/client/finality-grandpa/src/import.rs +++ b/client/finality-grandpa/src/import.rs @@ -30,7 +30,7 @@ use sp_consensus::{ BlockCheckParams, BlockImportParams, ImportResult, JustificationImport, SelectChain, }; -use sp_finality_grandpa::{GRANDPA_ENGINE_ID, ScheduledChange, ConsensusLog}; +use sp_finality_grandpa::{ConsensusLog, ScheduledChange, SetId, GRANDPA_ENGINE_ID}; use sp_runtime::Justification; use sp_runtime::generic::{BlockId, OpaqueDigestItemId}; use sp_runtime::traits::{ @@ -59,6 +59,7 @@ pub struct GrandpaBlockImport { authority_set: SharedAuthoritySet>, send_voter_commands: mpsc::UnboundedSender>>, consensus_changes: SharedConsensusChanges>, + authority_set_hard_forks: HashMap>>, _phantom: PhantomData, } @@ -72,6 +73,7 @@ impl Clone for authority_set: self.authority_set.clone(), send_voter_commands: self.send_voter_commands.clone(), consensus_changes: self.consensus_changes.clone(), + authority_set_hard_forks: self.authority_set_hard_forks.clone(), _phantom: PhantomData, } } @@ -212,9 +214,16 @@ where Client: crate::ClientForGrandpa, { // check for a new authority set change. - fn check_new_change(&self, header: &Block::Header, hash: Block::Hash) - -> Option>> - { + fn check_new_change( + &self, + header: &Block::Header, + hash: Block::Hash, + ) -> Option>> { + // check for forced authority set hard forks + if let Some(change) = self.authority_set_hard_forks.get(&hash) { + return Some(change.clone()); + } + // check for forced change. if let Some((median_last_finalized, change)) = find_forced_change::(header) { return Some(PendingChange { @@ -529,13 +538,50 @@ impl GrandpaBlockImport>, send_voter_commands: mpsc::UnboundedSender>>, consensus_changes: SharedConsensusChanges>, + authority_set_hard_forks: Vec<(SetId, PendingChange>)>, ) -> GrandpaBlockImport { + // check for and apply any forced authority set hard fork that applies + // to the *current* authority set. + if let Some((_, change)) = authority_set_hard_forks + .iter() + .find(|(set_id, _)| *set_id == authority_set.set_id()) + { + let mut authority_set = authority_set.inner().write(); + authority_set.current_authorities = change.next_authorities.clone(); + } + + // index authority set hard forks by block hash so that they can be used + // by any node syncing the chain and importing a block hard fork + // authority set changes. + let authority_set_hard_forks = authority_set_hard_forks + .into_iter() + .map(|(_, change)| (change.canon_hash, change)) + .collect::>(); + + // check for and apply any forced authority set hard fork that apply to + // any *pending* standard changes, checking by the block hash at which + // they were announced. + { + let mut authority_set = authority_set.inner().write(); + + authority_set.pending_standard_changes = authority_set + .pending_standard_changes + .clone() + .map(&mut |hash, _, original| { + authority_set_hard_forks + .get(&hash) + .cloned() + .unwrap_or(original) + }); + } + GrandpaBlockImport { inner, select_chain, authority_set, send_voter_commands, consensus_changes, + authority_set_hard_forks, _phantom: PhantomData, } } @@ -583,7 +629,7 @@ where match result { Err(CommandOrError::VoterCommand(command)) => { - info!(target: "afg", "Imported justification for block #{} that triggers \ + info!(target: "afg", "👴 Imported justification for block #{} that triggers \ command {}, signaling voter.", number, command); // send the command to the voter diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index 91da62848c534b7db461b82e01b0678a288a642a..d8e5846a56c1ee4c460d737775050523200d7f6c 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -63,6 +63,7 @@ use sc_client_api::{ }; use sp_blockchain::{HeaderBackend, Error as ClientError, HeaderMetadata}; use parity_scale_codec::{Decode, Encode}; +use prometheus_endpoint::{PrometheusError, Registry}; use sp_runtime::generic::BlockId; use sp_runtime::traits::{NumberFor, Block as BlockT, DigestFor, Zero}; use sc_keystore::KeyStorePtr; @@ -104,7 +105,7 @@ pub use voting_rule::{ }; use aux_schema::PersistentData; -use environment::{Environment, VoterSetState, Metrics}; +use environment::{Environment, VoterSetState}; use import::GrandpaBlockImport; use until_imported::UntilGlobalMessageBlocksImported; use communication::{NetworkBridge, Network as NetworkT}; @@ -417,10 +418,43 @@ pub fn block_import( client: Arc, genesis_authorities_provider: &dyn GenesisAuthoritySetProvider, select_chain: SC, -) -> Result<( +) -> Result< + ( GrandpaBlockImport, LinkHalf, - ), ClientError> + ), + ClientError, +> +where + SC: SelectChain, + BE: Backend + 'static, + Client: ClientForGrandpa + 'static, +{ + block_import_with_authority_set_hard_forks( + client, + genesis_authorities_provider, + select_chain, + Default::default(), + ) +} + +/// 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 +/// block when importing it) will be replaced by a standard change with the +/// given static authorities. +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)>, +) -> Result< + ( + GrandpaBlockImport, + LinkHalf, + ), + ClientError, +> where SC: SelectChain, BE: Backend + 'static, @@ -444,6 +478,24 @@ where let (voter_commands_tx, voter_commands_rx) = mpsc::unbounded(); + // create pending change objects with 0 delay and enacted on finality + // (i.e. standard changes) for each authority set hard fork. + let authority_set_hard_forks = authority_set_hard_forks + .into_iter() + .map(|(set_id, (hash, number), authorities)| { + ( + set_id, + authorities::PendingChange { + next_authorities: authorities, + delay: Zero::zero(), + canon_hash: hash, + canon_height: number, + delay_kind: authorities::DelayKind::Finalized, + }, + ) + }) + .collect(); + Ok(( GrandpaBlockImport::new( client.clone(), @@ -451,6 +503,7 @@ where persistent_data.authority_set.clone(), voter_commands_tx, persistent_data.consensus_changes.clone(), + authority_set_hard_forks, ), LinkHalf { client, @@ -467,6 +520,7 @@ fn global_communication( client: Arc, network: &NetworkBridge, keystore: &Option, + metrics: Option, ) -> ( impl Stream< Item = Result, CommandOrError>>, @@ -497,6 +551,7 @@ fn global_communication( client.clone(), global_in, "global", + metrics, ); let global_in = global_in.map_err(CommandOrError::from); @@ -644,6 +699,20 @@ pub fn run_grandpa_voter( Ok(future::select(voter_work, telemetry_task).map(drop)) } +struct Metrics { + environment: environment::Metrics, + until_imported: until_imported::Metrics, +} + +impl Metrics { + fn register(registry: &Registry) -> Result { + Ok(Metrics { + environment: environment::Metrics::register(registry)?, + until_imported: until_imported::Metrics::register(registry)?, + }) + } +} + /// Future that powers the voter. #[must_use] struct VoterWork, SC, VR> { @@ -651,6 +720,9 @@ struct VoterWork, SC, VR> { env: Arc>, voter_commands_rx: mpsc::UnboundedReceiver>>, network: NetworkBridge, + + /// Prometheus metrics. + metrics: Option, } impl VoterWork @@ -673,6 +745,14 @@ where voter_commands_rx: mpsc::UnboundedReceiver>>, prometheus_registry: Option, ) -> Self { + let metrics = match prometheus_registry.as_ref().map(Metrics::register) { + Some(Ok(metrics)) => Some(metrics), + Some(Err(e)) => { + debug!(target: "afg", "Failed to register metrics: {:?}", e); + None + } + None => None, + }; let voters = persistent_data.authority_set.current_authorities(); let env = Arc::new(Environment { @@ -686,10 +766,7 @@ where authority_set: persistent_data.authority_set.clone(), consensus_changes: persistent_data.consensus_changes.clone(), voter_set_state: persistent_data.set_state.clone(), - metrics: prometheus_registry.map(|registry| { - Metrics::register(®istry) - .expect("Other metrics would have failed to register before these; qed") - }), + metrics: metrics.as_ref().map(|m| m.environment.clone()), _phantom: PhantomData, }); @@ -700,6 +777,7 @@ where env, voter_commands_rx, network, + metrics, }; work.rebuild_voter(); work @@ -748,6 +826,7 @@ where self.env.client.clone(), &self.env.network, &self.env.config.keystore, + self.metrics.as_ref().map(|m| m.until_imported.clone()), ); let last_completed_round = completed_rounds.last(); diff --git a/client/finality-grandpa/src/observer.rs b/client/finality-grandpa/src/observer.rs index 971c2132904f6b325d6f76934a133fd178e87c0d..2382c6e2491d5d5f5fc39fd0804b49201775c84e 100644 --- a/client/finality-grandpa/src/observer.rs +++ b/client/finality-grandpa/src/observer.rs @@ -255,6 +255,7 @@ where self.client.clone(), &self.network, &self.keystore, + None, ); let last_finalized_number = self.client.info().finalized_number; @@ -425,21 +426,16 @@ mod tests { tester.trigger_gossip_validator_reputation_change(&peer_id); executor::block_on(async move { + // Poll the observer once and have it forward the reputation change from the gossip + // validator to the test network. + assert!(observer.now_or_never().is_none()); + // Ignore initial event stream request by gossip engine. match tester.events.next().now_or_never() { Some(Some(Event::EventStream(_))) => {}, _ => panic!("expected event stream request"), }; - assert!( - tester.events.next().now_or_never().is_none(), - "expect no further network events", - ); - - // Poll the observer once and have it forward the reputation change from the gossip - // validator to the test network. - assert!(observer.now_or_never().is_none()); - assert_matches!(tester.events.next().now_or_never(), Some(Some(Event::Report(_, _)))); }); } diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 50800f478476f02d36a346a519af9bb081d18dad..05ce90d3f1f753d7535c2dd98f7ea94833c4d3b1 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -30,21 +30,17 @@ use sp_keyring::Ed25519Keyring; use sc_client::LongestChain; use sc_client_api::backend::TransactionFor; use sp_blockchain::Result; -use sp_api::{ApiRef, ApiErrorExt, Core, RuntimeVersion, ApiExt, StorageProof, ProvideRuntimeApi}; +use sp_api::{ApiRef, StorageProof, ProvideRuntimeApi}; use substrate_test_runtime_client::runtime::BlockNumber; use sp_consensus::{ BlockOrigin, ForkChoiceStrategy, ImportedAux, BlockImportParams, ImportResult, BlockImport, import_queue::{BoxJustificationImport, BoxFinalityProofImport}, }; -use std::{ - collections::{HashMap, HashSet}, - result, - pin::Pin, -}; +use std::{collections::{HashMap, HashSet}, pin::Pin}; use parity_scale_codec::Decode; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, HashFor}; use sp_runtime::generic::{BlockId, DigestItem}; -use sp_core::{H256, NativeOrEncoded, ExecutionContext, crypto::Public}; +use sp_core::{H256, crypto::Public}; use sp_finality_grandpa::{GRANDPA_ENGINE_ID, AuthorityList, GrandpaApi}; use sp_state_machine::{InMemoryBackend, prove_read, read_proof_check}; @@ -214,87 +210,13 @@ impl ProvideRuntimeApi for TestApi { } } -impl Core for RuntimeApi { - fn Core_version_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - _: Option<()>, - _: Vec, - ) -> Result> { - unimplemented!("Not required for testing!") - } - - fn Core_execute_block_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - _: Option, - _: Vec, - ) -> Result> { - unimplemented!("Not required for testing!") - } +sp_api::mock_impl_runtime_apis! { + impl GrandpaApi for RuntimeApi { + type Error = sp_blockchain::Error; - fn Core_initialize_block_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - _: Option<&::Header>, - _: Vec, - ) -> Result> { - unimplemented!("Not required for testing!") - } -} - -impl ApiErrorExt for RuntimeApi { - type Error = sp_blockchain::Error; -} - -impl ApiExt for RuntimeApi { - type StateBackend = < - substrate_test_runtime_client::Backend as sc_client_api::backend::Backend - >::State; - - fn map_api_result result::Result, R, E>( - &self, - _: F - ) -> result::Result { - unimplemented!("Not required for testing!") - } - - fn runtime_version_at(&self, _: &BlockId) -> Result { - unimplemented!("Not required for testing!") - } - - fn record_proof(&mut self) { - unimplemented!("Not required for testing!") - } - - fn extract_proof(&mut self) -> Option { - unimplemented!("Not required for testing!") - } - - fn into_storage_changes( - &self, - _: &Self::StateBackend, - _: Option<&sp_api::ChangesTrieState, sp_api::NumberFor>>, - _: ::Hash, - ) -> std::result::Result, String> - where Self: Sized - { - unimplemented!("Not required for testing!") - } -} - -impl GrandpaApi for RuntimeApi { - fn GrandpaApi_grandpa_authorities_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - _: Option<()>, - _: Vec, - ) -> Result> { - Ok(self.inner.genesis_authorities.clone()).map(NativeOrEncoded::Native) + fn grandpa_authorities(&self) -> AuthorityList { + self.inner.genesis_authorities.clone() + } } } diff --git a/client/finality-grandpa/src/until_imported.rs b/client/finality-grandpa/src/until_imported.rs index c3a52fcf56fe197602ba844e61500686820f42cf..95bcceadedec476ba209325e794e2dffea36ff5d 100644 --- a/client/finality-grandpa/src/until_imported.rs +++ b/client/finality-grandpa/src/until_imported.rs @@ -29,48 +29,114 @@ use super::{ }; use log::{debug, warn}; -use sc_client_api::{BlockImportNotification, ImportNotifications}; use futures::prelude::*; use futures::stream::Fuse; use futures_timer::Delay; use futures::channel::mpsc::UnboundedReceiver; use finality_grandpa::voter; use parking_lot::Mutex; +use prometheus_endpoint::{ + Gauge, U64, PrometheusError, register, Registry, +}; +use sc_client_api::{BlockImportNotification, ImportNotifications}; +use sp_finality_grandpa::AuthorityId; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use std::collections::{HashMap, VecDeque}; use std::pin::Pin; -use std::sync::{atomic::{AtomicUsize, Ordering}, Arc}; +use std::sync::Arc; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; -use sp_finality_grandpa::AuthorityId; const LOG_PENDING_INTERVAL: Duration = Duration::from_secs(15); -// something which will block until imported. +/// Something that needs to be withheld until specific blocks are available. +/// +/// For example a GRANDPA commit message which is not of any use without the corresponding block +/// that it commits on. pub(crate) trait BlockUntilImported: Sized { - // the type that is blocked on. + /// The type that is blocked on. type Blocked; - /// new incoming item. For all internal items, - /// check if they require to be waited for. - /// if so, call the `Wait` closure. - /// if they are ready, call the `Ready` closure. - fn schedule_wait( + /// Check if a new incoming item needs awaiting until a block(s) is imported. + fn needs_waiting>( input: Self::Blocked, status_check: &S, - wait: Wait, - ready: Ready, - ) -> Result<(), Error> where - S: BlockStatusT, - Wait: FnMut(Block::Hash, NumberFor, Self), - Ready: FnMut(Self::Blocked); + ) -> Result, Error>; /// called when the wait has completed. The canonical number is passed through /// for further checks. fn wait_completed(self, canon_number: NumberFor) -> Option; } +/// Describes whether a given [`BlockUntilImported`] (a) should be discarded, (b) is waiting for +/// specific blocks to be imported or (c) is ready to be used. +/// +/// A reason for discarding a [`BlockUntilImported`] would be if a referenced block is perceived +/// under a different number than specified in the message. +pub(crate) enum DiscardWaitOrReady { + Discard, + Wait(Vec<(Block::Hash, NumberFor, W)>), + Ready(R), +} + +/// Prometheus metrics for the `UntilImported` queue. +// +// At a given point in time there can be more than one `UntilImported` queue. One can not register a +// metric twice, thus queues need to share the same Prometheus metrics instead of instantiating +// their own ones. +// +// When a queue is dropped it might still contain messages. In order for those to not distort the +// Prometheus metrics, the `Metric` struct cleans up after itself within its `Drop` implementation +// by subtracting the local_waiting_messages (the amount of messages left in the queue about to +// be dropped) from the global_waiting_messages gauge. +pub(crate) struct Metrics { + global_waiting_messages: Gauge, + local_waiting_messages: u64, +} + +impl Metrics { + pub(crate) fn register(registry: &Registry) -> Result { + Ok(Self { + global_waiting_messages: register(Gauge::new( + "finality_grandpa_until_imported_waiting_messages_number", + "Number of finality grandpa messages waiting within the until imported queue.", + )?, registry)?, + local_waiting_messages: 0, + }) + } + + fn waiting_messages_inc(&mut self) { + self.local_waiting_messages += 1; + self.global_waiting_messages.inc(); + } + + fn waiting_messages_dec(&mut self) { + self.local_waiting_messages -= 1; + self.global_waiting_messages.dec(); + } +} + + +impl Clone for Metrics { + fn clone(&self) -> Self { + Metrics { + global_waiting_messages: self.global_waiting_messages.clone(), + // When cloned, reset local_waiting_messages, so the global counter is not reduced a + // second time for the same messages on `drop` of the clone. + local_waiting_messages: 0, + } + } +} + +impl Drop for Metrics { + fn drop(&mut self) { + // Reduce the global counter by the amount of messages that were still left in the dropped + // queue. + self.global_waiting_messages.sub(self.local_waiting_messages) + } +} + /// Buffering imported messages until blocks with given hashes are imported. #[pin_project::pin_project] pub(crate) struct UntilImported> { @@ -80,12 +146,17 @@ pub(crate) struct UntilImported, ready: VecDeque, + /// Interval at which to check status of each awaited block. check_pending: Pin> + Send>>, /// Mapping block hashes to their block number, the point in time it was /// first encountered (Instant) and a list of GRANDPA messages referencing /// the block hash. pending: HashMap, Instant, Vec)>, + + /// Queue identifier for differentiation in logs. identifier: &'static str, + /// Prometheus metrics. + metrics: Option, } impl UntilImported where @@ -102,6 +173,7 @@ impl UntilImported, ) -> Self { // how often to check if pending messages that are waiting for blocks to be // imported can be checked. @@ -125,6 +197,7 @@ impl UntilImported Stream for UntilImported { // new input: schedule wait of any parts which require // blocks to be known. - let ready = &mut this.ready; - let pending = &mut this.pending; - M::schedule_wait( - input, - this.status_check, - |target_hash, target_number, wait| pending - .entry(target_hash) - .or_insert_with(|| (target_number, Instant::now(), Vec::new())) - .2 - .push(wait), - |ready_item| ready.push_back(ready_item), - )?; + match M::needs_waiting(input, this.status_check)? { + DiscardWaitOrReady::Discard => {}, + DiscardWaitOrReady::Wait(items) => { + for (target_hash, target_number, wait) in items { + this.pending + .entry(target_hash) + .or_insert_with(|| (target_number, Instant::now(), Vec::new())) + .2 + .push(wait) + } + }, + DiscardWaitOrReady::Ready(item) => this.ready.push_back(item), + } + + if let Some(metrics) = &mut this.metrics { + metrics.waiting_messages_inc(); + } } Poll::Pending => break, } @@ -231,6 +309,9 @@ impl Stream for UntilImported(hash: H, id: AuthorityId) impl BlockUntilImported for SignedMessage { type Blocked = Self; - fn schedule_wait( + fn needs_waiting>( msg: Self::Blocked, status_check: &BlockStatus, - mut wait: Wait, - mut ready: Ready, - ) -> Result<(), Error> where - BlockStatus: BlockStatusT, - Wait: FnMut(Block::Hash, NumberFor, Self), - Ready: FnMut(Self::Blocked), - { + ) -> Result, Error> { let (&target_hash, target_number) = msg.target(); if let Some(number) = status_check.block_number(target_hash)? { if number != target_number { warn_authority_wrong_target(target_hash, msg.id); + return Ok(DiscardWaitOrReady::Discard); } else { - ready(msg); + return Ok(DiscardWaitOrReady::Ready(msg)); } - } else { - wait(target_hash, target_number, msg) } - Ok(()) + return Ok(DiscardWaitOrReady::Wait(vec![(target_hash, target_number, msg)])) } fn wait_completed(self, canon_number: NumberFor) -> Option { @@ -307,8 +381,12 @@ pub(crate) type UntilVoteTargetImported { - inner: Arc<(AtomicUsize, Mutex>>)>, + inner: Arc>>>, target_number: NumberFor, } @@ -317,16 +395,10 @@ impl Unpin for BlockGlobalMessage {} impl BlockUntilImported for BlockGlobalMessage { type Blocked = CommunicationIn; - fn schedule_wait( + fn needs_waiting>( input: Self::Blocked, status_check: &BlockStatus, - mut wait: Wait, - mut ready: Ready, - ) -> Result<(), Error> where - BlockStatus: BlockStatusT, - Wait: FnMut(Block::Hash, NumberFor, Self), - Ready: FnMut(Self::Blocked), - { + ) -> Result, Error> { use std::collections::hash_map::Entry; enum KnownOrUnknown { @@ -344,7 +416,6 @@ impl BlockUntilImported for BlockGlobalMessage { } let mut checked_hashes: HashMap<_, KnownOrUnknown>> = HashMap::new(); - let mut unknown_count = 0; { // returns false when should early exit. @@ -359,7 +430,6 @@ impl BlockUntilImported for BlockGlobalMessage { } else { entry.insert(KnownOrUnknown::Unknown(perceived_number)); - unknown_count += 1; perceived_number } } @@ -384,7 +454,7 @@ impl BlockUntilImported for BlockGlobalMessage { for (target_number, target_hash) in precommit_targets { if !query_known(target_hash, target_number)? { - return Ok(()) + return Ok(DiscardWaitOrReady::Discard); } } }, @@ -402,66 +472,52 @@ impl BlockUntilImported for BlockGlobalMessage { for (target_number, target_hash) in targets { if !query_known(target_hash, target_number)? { - return Ok(()) + return Ok(DiscardWaitOrReady::Discard); } } }, }; } - // none of the hashes in the global message were unknown. - // we can just return the message directly. - if unknown_count == 0 { - ready(input); - return Ok(()) + let unknown_hashes = checked_hashes.into_iter().filter_map(|(hash, num)| match num { + KnownOrUnknown::Unknown(number) => Some((hash, number)), + KnownOrUnknown::Known(_) => None, + }).collect::>(); + + if unknown_hashes.is_empty() { + // none of the hashes in the global message were unknown. + // we can just return the message directly. + return Ok(DiscardWaitOrReady::Ready(input)); } - let locked_global = Arc::new((AtomicUsize::new(unknown_count), Mutex::new(Some(input)))); + let locked_global = Arc::new(Mutex::new(Some(input))); + + let items_to_await = unknown_hashes.into_iter().map(|(hash, target_number)| { + (hash, target_number, BlockGlobalMessage { inner: locked_global.clone(), target_number }) + }).collect(); // schedule waits for all unknown messages. // when the last one of these has `wait_completed` called on it, // the global message will be returned. - // - // in the future, we may want to issue sync requests to the network - // if this is taking a long time. - for (hash, is_known) in checked_hashes { - if let KnownOrUnknown::Unknown(target_number) = is_known { - wait(hash, target_number, BlockGlobalMessage { - inner: locked_global.clone(), - target_number, - }) - } - } - - Ok(()) + Ok(DiscardWaitOrReady::Wait(items_to_await)) } fn wait_completed(self, canon_number: NumberFor) -> Option { if self.target_number != canon_number { - // if we return without deducting the counter, then none of the other - // handles can return the commit message. + // Delete the inner message so it won't ever be forwarded. Future calls to + // `wait_completed` on the same `inner` will ignore it. + *self.inner.lock() = None; return None; } - let mut last_count = self.inner.0.load(Ordering::Acquire); - - // CAS loop to ensure that we always have a last reader. - loop { - if last_count == 1 { // we are the last one left. - return self.inner.1.lock().take(); - } - - let prev_value = self.inner.0.compare_and_swap( - last_count, - last_count - 1, - Ordering::SeqCst, - ); - - if prev_value == last_count { - return None; - } else { - last_count = prev_value; - } + match Arc::try_unwrap(self.inner) { + // This is the last reference and thus the last outstanding block to be awaited. `inner` + // is either `Some(_)` or `None`. The latter implies that a previous `wait_completed` + // call witnessed a block number mismatch (see above). + Ok(inner) => Mutex::into_inner(inner), + // There are still other strong references to this `Arc`, thus the message is blocked on + // other blocks to be imported. + Err(_) => None, } } } @@ -601,6 +657,7 @@ mod tests { block_status, global_rx, "global", + None, ); global_tx.unbounded_send(msg).unwrap(); @@ -627,6 +684,7 @@ mod tests { block_status, global_rx, "global", + None, ); global_tx.unbounded_send(msg).unwrap(); @@ -881,6 +939,7 @@ mod tests { block_status, global_rx, "global", + None, ); let h1 = make_header(5); @@ -941,4 +1000,88 @@ mod tests { futures::executor::block_on(test); } + + fn test_catch_up() -> Arc>>> { + let header = make_header(5); + + let unknown_catch_up = finality_grandpa::CatchUp { + round_number: 1, + precommits: vec![], + prevotes: vec![], + base_hash: header.hash(), + base_number: *header.number(), + }; + + let catch_up = voter::CommunicationIn::CatchUp( + unknown_catch_up.clone(), + voter::Callback::Blank, + ); + + Arc::new(Mutex::new(Some(catch_up))) + } + + #[test] + fn block_global_message_wait_completed_return_when_all_awaited() { + let msg_inner = test_catch_up(); + + let waiting_block_1 = BlockGlobalMessage:: { + inner: msg_inner.clone(), + target_number: 1, + }; + + let waiting_block_2 = BlockGlobalMessage:: { + inner: msg_inner, + target_number: 2, + }; + + // waiting_block_2 is still waiting for block 2, thus this should return `None`. + assert!(waiting_block_1.wait_completed(1).is_none()); + + // Message only depended on block 1 and 2. Both have been imported, thus this should yield + // the message. + assert!(waiting_block_2.wait_completed(2).is_some()); + } + + #[test] + fn block_global_message_wait_completed_return_none_on_block_number_missmatch() { + let msg_inner = test_catch_up(); + + let waiting_block_1 = BlockGlobalMessage:: { + inner: msg_inner.clone(), + target_number: 1, + }; + + let waiting_block_2 = BlockGlobalMessage:: { + inner: msg_inner, + target_number: 2, + }; + + // Calling wait_completed with wrong block number should yield None. + assert!(waiting_block_1.wait_completed(1234).is_none()); + + // All blocks, that the message depended on, have been imported. Still, given the above + // block number mismatch this should return None. + assert!(waiting_block_2.wait_completed(2).is_none()); + } + + #[test] + fn metrics_cleans_up_after_itself() { + let r = Registry::new(); + + let mut m1 = Metrics::register(&r).unwrap(); + let m2 = m1.clone(); + + // Add a new message to the 'queue' of m1. + m1.waiting_messages_inc(); + + // m1 and m2 are synced through the shared atomic. + assert_eq!(1, m2.global_waiting_messages.get()); + + // Drop 'queue' m1. + drop(m1); + + // Make sure m1 cleaned up after itself, removing all messages that were left in its queue + // when dropped from the global metric. + assert_eq!(0, m2.global_waiting_messages.get()); + } } diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index d37bfb1cc29648c096e745ea57ac793e17f16529..b60886d6dea6cfbb7d4b9151f2a2498b81be4404 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-informant" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Substrate informant." edition = "2018" @@ -10,12 +10,15 @@ repository = "https://github.com/paritytech/substrate/" [dependencies] ansi_term = "0.12.1" -futures = "0.3.1" +futures = "0.3.4" log = "0.4.8" -parity-util-mem = { version = "0.5.2", default-features = false, features = ["primitive-types"] } +parity-util-mem = { version = "0.6.0", default-features = false, features = ["primitive-types"] } wasm-timer = "0.2" -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sc-network = { version = "0.8.0-alpha.2", path = "../network" } -sc-service = { version = "0.8.0-alpha.2", default-features = false, path = "../service" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sc-network = { version = "0.8.0-alpha.5", path = "../network" } +sc-service = { version = "0.8.0-alpha.5", default-features = false, path = "../service" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/informant/src/display.rs b/client/informant/src/display.rs index 53c9697868348cb9a73328d26a459601196cd240..f08c1f04c75f73a968ee49fba5232b4d5b25fa4b 100644 --- a/client/informant/src/display.rs +++ b/client/informant/src/display.rs @@ -68,9 +68,9 @@ impl InformantDisplay { self.last_number = Some(best_number); let (status, target) = match (net_status.sync_state, net_status.best_seen_block) { - (SyncState::Idle, _) => ("Idle".into(), "".into()), - (SyncState::Downloading, None) => (format!("Syncing{}", speed), "".into()), - (SyncState::Downloading, Some(n)) => (format!("Syncing{}", speed), format!(", target=#{}", n)), + (SyncState::Idle, _) => ("💤 Idle".into(), "".into()), + (SyncState::Downloading, None) => (format!("⚙️ Syncing{}", speed), "".into()), + (SyncState::Downloading, Some(n)) => (format!("⚙️ Syncing{}", speed), format!(", target=#{}", n)), }; if self.format == OutputFormat::Coloured { diff --git a/client/informant/src/lib.rs b/client/informant/src/lib.rs index d104a64a2dbd121a00d27dde8de80f61fee0c12d..5c2ac41d445e39ad2599b21e682aac304a1b38e1 100644 --- a/client/informant/src/lib.rs +++ b/client/informant/src/lib.rs @@ -79,7 +79,7 @@ pub fn build(service: &impl AbstractService, format: OutputFormat) -> impl futur match maybe_ancestor { Ok(ref ancestor) if ancestor.hash != *last_hash => info!( - "Reorg from #{},{} to #{},{}, common ancestor #{},{}", + "♻️ Reorg from #{},{} to #{},{}, common ancestor #{},{}", last_num, last_hash, n.header.number(), n.hash, ancestor.number, ancestor.hash, @@ -94,7 +94,7 @@ pub fn build(service: &impl AbstractService, format: OutputFormat) -> impl futur last_best = Some((n.header.number().clone(), n.hash.clone())); } - info!(target: "substrate", "Imported #{} ({})", n.header.number(), n.hash); + info!(target: "substrate", "✨ Imported #{} ({})", n.header.number(), n.hash); future::ready(()) }); diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index 247376bc46e17952cf6c1551becb4324cba90ba7..af9cdf81bfe9a472f12dd4284001b95934ce0bd7 100644 --- a/client/keystore/Cargo.toml +++ b/client/keystore/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-keystore" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,8 +12,8 @@ documentation = "https://docs.rs/sc-keystore" [dependencies] derive_more = "0.99.2" -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-application-crypto = { version = "2.0.0-alpha.2", path = "../../primitives/application-crypto" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-application-crypto = { version = "2.0.0-alpha.5", path = "../../primitives/application-crypto" } hex = "0.4.0" rand = "0.7.2" serde_json = "1.0.41" @@ -22,3 +22,6 @@ parking_lot = "0.10.0" [dev-dependencies] tempfile = "3.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/keystore/src/lib.rs b/client/keystore/src/lib.rs index 087ddf326de0141cc011cf4b046779193b4ba2a1..f8bc93097113b300bc85373918021da742700221 100644 --- a/client/keystore/src/lib.rs +++ b/client/keystore/src/lib.rs @@ -17,10 +17,11 @@ //! Keystore (and session key management) for ed25519 based chains like Polkadot. #![warn(missing_docs)] - -use std::{collections::HashMap, path::PathBuf, fs::{self, File}, io::{self, Write}, sync::Arc}; +use std::{collections::{HashMap, HashSet}, path::PathBuf, fs::{self, File}, io::{self, Write}, sync::Arc}; use sp_core::{ - crypto::{KeyTypeId, Pair as PairT, Public, IsWrappedBy, Protected}, traits::BareCryptoStore, + crypto::{IsWrappedBy, CryptoTypePublicPair, KeyTypeId, Pair as PairT, Protected, Public}, + traits::{BareCryptoStore, BareCryptoStoreError as TraitError}, + Encode, }; use sp_application_crypto::{AppKey, AppPublic, AppPair, ed25519, sr25519}; use parking_lot::RwLock; @@ -44,6 +45,12 @@ pub enum Error { /// Invalid seed #[display(fmt="Invalid seed")] InvalidSeed, + /// Public key type is not supported + #[display(fmt="Key crypto type is not supported")] + KeyNotSupported(KeyTypeId), + /// Pair not found for public key and KeyTypeId + #[display(fmt="Pair not found for {} public key", "_0")] + PairNotFound(String), /// Keystore unavailable #[display(fmt="Keystore unavailable")] Unavailable, @@ -52,6 +59,21 @@ pub enum Error { /// Keystore Result pub type Result = std::result::Result; +impl From for TraitError { + fn from(error: Error) -> Self { + match error { + Error::KeyNotSupported(id) => TraitError::KeyNotSupported(id), + Error::PairNotFound(e) => TraitError::PairNotFound(e), + Error::InvalidSeed | Error::InvalidPhrase | Error::InvalidPassword => { + TraitError::ValidationError(error.to_string()) + }, + Error::Unavailable => TraitError::Unavailable, + Error::Io(e) => TraitError::Other(e.to_string()), + Error::Json(e) => TraitError::Other(e.to_string()), + } + } +} + impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -220,16 +242,35 @@ impl Store { self.key_pair_by_type::(IsWrappedBy::from_ref(public), Pair::ID).map(Into::into) } - /// Get public keys of all stored keys that match the given key type. - pub fn public_keys_by_type(&self, key_type: KeyTypeId) -> Result> { - let mut public_keys: Vec = self.additional.keys() - .filter_map(|(ty, public)| { - if *ty == key_type { - Some(TPublic::from_slice(public)) - } else { - None - } + /// Get public keys of all stored keys that match the key type. + /// + /// This will just use the type of the public key (a list of which to be returned) in order + /// to determine the key type. Unless you use a specialized application-type public key, then + /// this only give you keys registered under generic cryptography, and will not return keys + /// registered under the application type. + pub 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() }) + } + + /// Returns the file path for the given public key and key type. + 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); + buf.push(key_type + key.as_str()); + Some(buf) + } + + /// Returns a list of raw public keys filtered by `KeyTypeId` + fn raw_public_keys(&self, id: KeyTypeId) -> Result>> { + let mut public_keys: Vec> = self.additional.keys() + .into_iter() + .filter_map(|k| if k.0 == id { Some(k.1.clone()) } else { None }) .collect(); if let Some(path) = &self.path { @@ -241,8 +282,10 @@ impl Store { if let Some(name) = path.file_name().and_then(|n| n.to_str()) { match hex::decode(name) { Ok(ref hex) if hex.len() > 4 => { - if &hex[0..4] != &key_type.0 { continue } - let public = TPublic::from_slice(&hex[4..]); + if &hex[0..4] != &id.0 { + continue; + } + let public = hex[4..].to_vec(); public_keys.push(public); } _ => continue, @@ -253,71 +296,104 @@ impl Store { Ok(public_keys) } +} - /// Get public keys of all stored keys that match the key type. - /// - /// This will just use the type of the public key (a list of which to be returned) in order - /// to determine the key type. Unless you use a specialized application-type public key, then - /// this only give you keys registered under generic cryptography, and will not return keys - /// registered under the application type. - pub fn public_keys(&self) -> Result> { - self.public_keys_by_type::(Public::ID) - .map(|v| v.into_iter().map(Into::into).collect()) +impl BareCryptoStore for Store { + fn keys( + &self, + id: KeyTypeId + ) -> std::result::Result, TraitError> { + let raw_keys = self.raw_public_keys(id)?; + Ok(raw_keys.into_iter() + .fold(Vec::new(), |mut v, k| { + v.push(CryptoTypePublicPair(sr25519::CRYPTO_ID, k.clone())); + v.push(CryptoTypePublicPair(ed25519::CRYPTO_ID, k.clone())); + v + })) + } + + fn supported_keys( + &self, + id: KeyTypeId, + keys: Vec + ) -> std::result::Result, TraitError> { + let all_keys = self.keys(id)?.into_iter().collect::>(); + Ok(keys.into_iter() + .filter(|key| all_keys.contains(key)) + .collect::>()) } - /// Returns the file path for the given public key and key type. - 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); - buf.push(key_type + key.as_str()); - Some(buf) + fn sign_with( + &self, + id: KeyTypeId, + key: &CryptoTypePublicPair, + msg: &[u8], + ) -> std::result::Result, TraitError> { + match key.0 { + ed25519::CRYPTO_ID => { + let pub_key = ed25519::Public::from_slice(key.1.as_slice()); + let key_pair: ed25519::Pair = self + .key_pair_by_type::(&pub_key, id) + .map_err(|e| TraitError::from(e))?; + Ok(key_pair.sign(msg).encode()) + } + sr25519::CRYPTO_ID => { + let pub_key = sr25519::Public::from_slice(key.1.as_slice()); + let key_pair: sr25519::Pair = self + .key_pair_by_type::(&pub_key, id) + .map_err(|e| TraitError::from(e))?; + Ok(key_pair.sign(msg).encode()) + } + _ => Err(TraitError::KeyNotSupported(id)) + } } -} -impl BareCryptoStore for Store { fn sr25519_public_keys(&self, key_type: KeyTypeId) -> Vec { - self.public_keys_by_type::(key_type).unwrap_or_default() + self.raw_public_keys(key_type) + .map(|v| { + v.into_iter() + .map(|k| sr25519::Public::from_slice(k.as_slice())) + .collect() + }) + .unwrap_or_default() } fn sr25519_generate_new( &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> std::result::Result { + ) -> std::result::Result { let pair = match seed { Some(seed) => self.insert_ephemeral_from_seed_by_type::(seed, id), None => self.generate_by_type::(id), - }.map_err(|e| e.to_string())?; + }.map_err(|e| -> TraitError { e.into() })?; Ok(pair.public()) } - fn sr25519_key_pair(&self, id: KeyTypeId, pub_key: &sr25519::Public) -> Option { - self.key_pair_by_type::(pub_key, id).ok() - } - fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { - self.public_keys_by_type::(key_type).unwrap_or_default() + self.raw_public_keys(key_type) + .map(|v| { + v.into_iter() + .map(|k| ed25519::Public::from_slice(k.as_slice())) + .collect() + }) + .unwrap_or_default() } fn ed25519_generate_new( &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> std::result::Result { + ) -> std::result::Result { let pair = match seed { Some(seed) => self.insert_ephemeral_from_seed_by_type::(seed, id), None => self.generate_by_type::(id), - }.map_err(|e| e.to_string())?; + }.map_err(|e| -> TraitError { e.into() })?; Ok(pair.public()) } - fn ed25519_key_pair(&self, id: KeyTypeId, pub_key: &ed25519::Public) -> Option { - self.key_pair_by_type::(pub_key, id).ok() - } - fn insert_unknown(&mut self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> std::result::Result<(), ()> { @@ -337,7 +413,7 @@ impl BareCryptoStore for Store { mod tests { use super::*; use tempfile::TempDir; - use sp_core::{testing::{SR25519}, crypto::{Ss58Codec}}; + use sp_core::{testing::SR25519, crypto::Ss58Codec}; #[test] fn basic_store() { @@ -451,7 +527,7 @@ mod tests { fs::write(file_name, "test").expect("Invalid file is written"); assert!( - store.read().public_keys_by_type::(SR25519).unwrap().is_empty(), + store.read().sr25519_public_keys(SR25519).is_empty(), ); } } diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 955911f1f07c1777efc83332cf638b721b396f24..153e8103ded7d59e03b6a4fc46038742fa9e6a04 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Gossiping for the Substrate network protocol" name = "sc-network-gossip" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" license = "GPL-3.0" authors = ["Parity Technologies "] edition = "2018" @@ -11,11 +11,17 @@ documentation = "https://docs.rs/sc-network-gossip" [dependencies] -futures = "0.3.1" +futures = "0.3.4" futures-timer = "3.0.1" libp2p = { version = "0.16.2", default-features = false, features = ["libp2p-websocket"] } log = "0.4.8" lru = "0.4.3" -sc-network = { version = "0.8.0-alpha.2", path = "../network" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } +sc-network = { version = "0.8.0-alpha.5", path = "../network" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } wasm-timer = "0.2" + +[dev-dependencies] +substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/network-gossip/src/bridge.rs b/client/network-gossip/src/bridge.rs index c06cb6268cc8063f56c3fe7dd4b3ed12b5f65c94..0b3a58bba2746421828362e314dc8df6590e7cba 100644 --- a/client/network-gossip/src/bridge.rs +++ b/client/network-gossip/src/bridge.rs @@ -17,7 +17,6 @@ use crate::{Network, Validator}; use crate::state_machine::{ConsensusGossip, TopicNotification, PERIODIC_MAINTENANCE_INTERVAL}; -use sc_network::message::generic::ConsensusMessage; use sc_network::{Event, ReputationChange}; use futures::{prelude::*, channel::mpsc}; @@ -77,12 +76,7 @@ impl GossipEngine { topic: B::Hash, message: Vec, ) { - let message = ConsensusMessage { - engine_id: self.engine_id, - data: message, - }; - - self.state_machine.register_message(topic, message); + self.state_machine.register_message(topic, self.engine_id, message); } /// Broadcast all messages with given topic. @@ -114,22 +108,14 @@ impl GossipEngine { message: Vec, force: bool, ) { - let message = ConsensusMessage { - engine_id: self.engine_id, - data: message, - }; - - self.state_machine.multicast(&mut *self.network, topic, message, force) + self.state_machine.multicast(&mut *self.network, topic, self.engine_id, message, force) } /// 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) { for who in &who { - self.state_machine.send_message(&mut *self.network, who, ConsensusMessage { - engine_id: self.engine_id, - data: data.clone(), - }); + self.state_machine.send_message(&mut *self.network, who, self.engine_id, data.clone()); } } @@ -148,33 +134,38 @@ impl Future for GossipEngine { fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { let this = &mut *self; - while let Poll::Ready(Some(event)) = this.network_event_stream.poll_next_unpin(cx) { - match event { - Event::NotificationStreamOpened { remote, engine_id: msg_engine_id, roles } => { - if msg_engine_id != this.engine_id { - continue; + loop { + match this.network_event_stream.poll_next_unpin(cx) { + Poll::Ready(Some(event)) => match event { + Event::NotificationStreamOpened { remote, engine_id: msg_engine_id, roles } => { + if msg_engine_id != this.engine_id { + continue; + } + this.state_machine.new_peer(&mut *this.network, remote, roles); } - this.state_machine.new_peer(&mut *this.network, remote, roles); + Event::NotificationStreamClosed { remote, engine_id: msg_engine_id } => { + if msg_engine_id != this.engine_id { + continue; + } + this.state_machine.peer_disconnected(&mut *this.network, remote); + }, + Event::NotificationsReceived { remote, messages } => { + let engine_id = this.engine_id.clone(); + this.state_machine.on_incoming( + &mut *this.network, + remote, + messages.into_iter() + .filter_map(|(engine, data)| if engine == engine_id { + Some((engine, data.to_vec())) + } else { None }) + .collect() + ); + }, + Event::Dht(_) => {} } - Event::NotificationStreamClosed { remote, engine_id: msg_engine_id } => { - if msg_engine_id != this.engine_id { - continue; - } - this.state_machine.peer_disconnected(&mut *this.network, remote); - }, - Event::NotificationsReceived { remote, messages } => { - let engine_id = this.engine_id.clone(); - this.state_machine.on_incoming( - &mut *this.network, - remote, - messages.into_iter() - .filter_map(|(engine, data)| if engine == engine_id { - Some(ConsensusMessage { engine_id: engine, data: data.to_vec() }) - } else { None }) - .collect() - ); - }, - Event::Dht(_) => {} + // The network event stream closed. Do the same for [`GossipValidator`]. + Poll::Ready(None) => return Poll::Ready(()), + Poll::Pending => break, } } @@ -186,3 +177,77 @@ impl Future for GossipEngine { Poll::Pending } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ValidationResult, ValidatorContext}; + use substrate_test_runtime_client::runtime::Block; + + struct TestNetwork {} + + impl Network for Arc { + fn event_stream(&self) -> Pin + Send>> { + let (_tx, rx) = futures::channel::mpsc::channel(0); + + // Return rx and drop tx. Thus the given channel will yield `Poll::Ready(None)` on first + // poll. + Box::pin(rx) + } + + fn report_peer(&self, _: PeerId, _: ReputationChange) { + unimplemented!(); + } + + fn disconnect_peer(&self, _: PeerId) { + unimplemented!(); + } + + fn write_notification(&self, _: PeerId, _: ConsensusEngineId, _: Vec) { + unimplemented!(); + } + + fn register_notifications_protocol(&self, _: ConsensusEngineId, _: Cow<'static, [u8]>) {} + + fn announce(&self, _: B::Hash, _: Vec) { + unimplemented!(); + } + } + + struct TestValidator {} + + impl Validator for TestValidator { + fn validate( + &self, + _: &mut dyn ValidatorContext, + _: &PeerId, + _: &[u8] + ) -> ValidationResult { + unimplemented!(); + } + } + + /// Regression test for the case where the `GossipEngine.network_event_stream` closes. One + /// should not ignore a `Poll::Ready(None)` as `poll_next_unpin` will panic on subsequent calls. + /// + /// See https://github.com/paritytech/substrate/issues/5000 for details. + #[test] + fn returns_when_network_event_stream_closes() { + let mut gossip_engine = GossipEngine::::new( + Arc::new(TestNetwork{}), + [1, 2, 3, 4], + "my_protocol".as_bytes(), + Arc::new(TestValidator{}), + ); + + futures::executor::block_on(futures::future::poll_fn(move |ctx| { + if let Poll::Pending = gossip_engine.poll_unpin(ctx) { + panic!( + "Expected gossip engine to finish on first poll, given that \ + `GossipEngine.network_event_stream` closes right away." + ) + } + Poll::Ready(()) + })) + } +} diff --git a/client/network-gossip/src/state_machine.rs b/client/network-gossip/src/state_machine.rs index 675f0213651d631b012535dd0e2cdfad1cb1b565..e2d7ec45b57a4c57cdda8a566b3402f115df64dd 100644 --- a/client/network-gossip/src/state_machine.rs +++ b/client/network-gossip/src/state_machine.rs @@ -20,13 +20,12 @@ use std::collections::{HashMap, HashSet, hash_map::Entry}; use std::sync::Arc; use std::iter; use std::time; -use log::{trace, debug}; +use log::trace; use futures::channel::mpsc; use lru::LruCache; use libp2p::PeerId; use sp_runtime::traits::{Block as BlockT, Hash, HashFor}; use sp_runtime::ConsensusEngineId; -pub use sc_network::message::generic::{Message, ConsensusMessage}; use sc_network::config::Roles; use wasm_timer::Instant; @@ -67,7 +66,8 @@ pub struct TopicNotification { struct MessageEntry { message_hash: B::Hash, topic: B::Hash, - message: ConsensusMessage, + engine_id: ConsensusEngineId, + message: Vec, sender: Option, } @@ -89,7 +89,8 @@ impl<'g, 'p, B: BlockT> ValidatorContext for NetworkContext<'g, 'p, B> { self.gossip.multicast( self.network, topic, - ConsensusMessage{ data: message, engine_id: self.engine_id.clone() }, + self.engine_id.clone(), + message, force, ); } @@ -113,11 +114,10 @@ fn propagate<'a, B: BlockT, I>( validators: &HashMap>>, ) // (msg_hash, topic, message) - where I: Clone + IntoIterator, + where I: Clone + IntoIterator)>, { let mut check_fns = HashMap::new(); - let mut message_allowed = move |who: &PeerId, intent: MessageIntent, topic: &B::Hash, message: &ConsensusMessage| { - let engine_id = message.engine_id; + let mut message_allowed = move |who: &PeerId, intent: MessageIntent, topic: &B::Hash, engine_id: ConsensusEngineId, message: &Vec| { let check_fn = match check_fns.entry(engine_id) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(vacant) => match validators.get(&engine_id) { @@ -126,11 +126,11 @@ fn propagate<'a, B: BlockT, I>( } }; - (check_fn)(who, intent, topic, &message.data) + (check_fn)(who, intent, topic, &message) }; for (id, ref mut peer) in peers.iter_mut() { - for (message_hash, topic, message) in messages.clone() { + for (message_hash, topic, engine_id, message) in messages.clone() { let intent = match intent { MessageIntent::Broadcast { .. } => if peer.known_messages.contains(&message_hash) { @@ -149,14 +149,14 @@ fn propagate<'a, B: BlockT, I>( other => other, }; - if !message_allowed(id, intent, &topic, &message) { + if !message_allowed(id, intent, &topic, engine_id, &message) { continue; } peer.known_messages.insert(message_hash.clone()); trace!(target: "gossip", "Propagating to {}: {:?}", id, message); - network.write_notification(id.clone(), message.engine_id, message.data.clone()); + network.write_notification(id.clone(), engine_id, message.clone()); } } } @@ -225,13 +225,15 @@ impl ConsensusGossip { &mut self, message_hash: B::Hash, topic: B::Hash, - message: ConsensusMessage, + engine_id: ConsensusEngineId, + message: Vec, sender: Option, ) { if self.known_messages.put(message_hash.clone(), ()).is_none() { self.messages.push(MessageEntry { message_hash, topic, + engine_id, message, sender, }); @@ -246,10 +248,11 @@ impl ConsensusGossip { pub fn register_message( &mut self, topic: B::Hash, - message: ConsensusMessage, + engine_id: ConsensusEngineId, + message: Vec, ) { - let message_hash = HashFor::::hash(&message.data[..]); - self.register_message_hashed(message_hash, topic, message, None); + let message_hash = HashFor::::hash(&message[..]); + self.register_message_hashed(message_hash, topic, engine_id, message, None); } /// Call when a peer has been disconnected to stop tracking gossip status. @@ -273,7 +276,7 @@ impl ConsensusGossip { /// Rebroadcast all messages to all peers. fn rebroadcast(&mut self, network: &mut dyn Network) { let messages = self.messages.iter() - .map(|entry| (&entry.message_hash, &entry.topic, &entry.message)); + .map(|entry| (&entry.message_hash, &entry.topic, entry.engine_id, &entry.message)); propagate(network, messages, MessageIntent::PeriodicRebroadcast, &mut self.peers, &self.validators); } @@ -281,7 +284,9 @@ impl ConsensusGossip { pub fn broadcast_topic(&mut self, network: &mut dyn Network, topic: B::Hash, force: bool) { let messages = self.messages.iter() .filter_map(|entry| - if entry.topic == topic { Some((&entry.message_hash, &entry.topic, &entry.message)) } else { None } + if entry.topic == topic { + Some((&entry.message_hash, &entry.topic, entry.engine_id, &entry.message)) + } else { None } ); let intent = if force { MessageIntent::ForcedBroadcast } else { MessageIntent::Broadcast }; propagate(network, messages, intent, &mut self.peers, &self.validators); @@ -301,7 +306,7 @@ impl ConsensusGossip { let mut check_fns = HashMap::new(); let mut message_expired = move |entry: &MessageEntry| { - let engine_id = entry.message.engine_id; + let engine_id = entry.engine_id; let check_fn = match check_fns.entry(engine_id) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(vacant) => match validators.get(&engine_id) { @@ -310,7 +315,7 @@ impl ConsensusGossip { } }; - (check_fn)(entry.topic, &entry.message.data) + (check_fn)(entry.topic, &entry.message) }; self.messages.retain(|entry| !message_expired(entry)); @@ -332,10 +337,10 @@ impl ConsensusGossip { { let (tx, rx) = mpsc::unbounded(); for entry in self.messages.iter_mut() - .filter(|e| e.topic == topic && e.message.engine_id == engine_id) + .filter(|e| e.topic == topic && e.engine_id == engine_id) { tx.unbounded_send(TopicNotification { - message: entry.message.data.clone(), + message: entry.message.clone(), sender: entry.sender.clone(), }) .expect("receiver known to be live; qed"); @@ -346,22 +351,21 @@ impl ConsensusGossip { rx } - /// Handle an incoming ConsensusMessage for topic by who via protocol. Discard message if topic - /// already known, the message is old, its source peers isn't a registered peer or the connection - /// to them is broken. Return `Some(topic, message)` if it was added to the internal queue, `None` - /// in all other cases. + /// Handle an incoming message for topic by who via protocol. Discard message if topic already + /// known, the message is old, its source peers isn't a registered peer or the connection to + /// them is broken. pub fn on_incoming( &mut self, network: &mut dyn Network, who: PeerId, - messages: Vec, + messages: Vec<(ConsensusEngineId, Vec)>, ) { if !messages.is_empty() { trace!(target: "gossip", "Received {} messages from peer {}", messages.len(), who); } - for message in messages { - let message_hash = HashFor::::hash(&message.data[..]); + for (engine_id, message) in messages { + let message_hash = HashFor::::hash(&message[..]); if self.known_messages.contains(&message_hash) { trace!(target:"gossip", "Ignored already known message from {}", who); @@ -369,13 +373,12 @@ impl ConsensusGossip { continue; } - let engine_id = message.engine_id; // validate the message let validation = self.validators.get(&engine_id) .cloned() .map(|v| { let mut context = NetworkContext { gossip: self, network, engine_id }; - v.validate(&mut context, &who, &message.data) + v.validate(&mut context, &who, &message) }); let validation_result = match validation { @@ -395,10 +398,10 @@ impl ConsensusGossip { if let Some(ref mut peer) = self.peers.get_mut(&who) { peer.known_messages.insert(message_hash); if let Entry::Occupied(mut entry) = self.live_message_sinks.entry((engine_id, topic)) { - debug!(target: "gossip", "Pushing consensus message to sinks for {}.", topic); + trace!(target: "gossip", "Pushing consensus message to sinks for {}.", topic); entry.get_mut().retain(|sink| { if let Err(e) = sink.unbounded_send(TopicNotification { - message: message.data.clone(), + message: message.clone(), sender: Some(who.clone()) }) { trace!(target: "gossip", "Error broadcasting message notification: {:?}", e); @@ -410,14 +413,14 @@ impl ConsensusGossip { } } if keep { - self.register_message_hashed(message_hash, topic, message, Some(who.clone())); + self.register_message_hashed(message_hash, topic, engine_id, message, Some(who.clone())); } } else { trace!(target:"gossip", "Ignored statement from unregistered peer {}", who); network.report_peer(who.clone(), rep::UNREGISTERED_TOPIC); } } else { - trace!(target:"gossip", "Handled valid one hop message from peer {}", who); + trace!(target:"gossip", "Discard message from peer {}", who); } } } @@ -438,7 +441,7 @@ impl ConsensusGossip { }; if let Some(ref mut peer) = self.peers.get_mut(who) { - for entry in self.messages.iter().filter(|m| m.topic == topic && m.message.engine_id == engine_id) { + for entry in self.messages.iter().filter(|m| m.topic == topic && m.engine_id == engine_id) { let intent = if force { MessageIntent::ForcedBroadcast } else { @@ -449,14 +452,14 @@ impl ConsensusGossip { continue; } - if !message_allowed(who, intent, &entry.topic, &entry.message.data) { + if !message_allowed(who, intent, &entry.topic, &entry.message) { continue; } peer.known_messages.insert(entry.message_hash.clone()); trace!(target: "gossip", "Sending topic message to {}: {:?}", who, entry.message); - network.write_notification(who.clone(), engine_id, entry.message.data.clone()); + network.write_notification(who.clone(), engine_id, entry.message.clone()); } } } @@ -466,13 +469,14 @@ impl ConsensusGossip { &mut self, network: &mut dyn Network, topic: B::Hash, - message: ConsensusMessage, + engine_id: ConsensusEngineId, + message: Vec, force: bool, ) { - let message_hash = HashFor::::hash(&message.data); - self.register_message_hashed(message_hash, topic, message.clone(), None); + let message_hash = HashFor::::hash(&message); + self.register_message_hashed(message_hash, topic, engine_id, message.clone(), None); let intent = if force { MessageIntent::ForcedBroadcast } else { MessageIntent::Broadcast }; - propagate(network, iter::once((&message_hash, &topic, &message)), intent, &mut self.peers, &self.validators); + propagate(network, iter::once((&message_hash, &topic, engine_id, &message)), intent, &mut self.peers, &self.validators); } /// Send addressed message to a peer. The message is not kept or multicast @@ -481,19 +485,20 @@ impl ConsensusGossip { &mut self, network: &mut dyn Network, who: &PeerId, - message: ConsensusMessage, + engine_id: ConsensusEngineId, + message: Vec, ) { let peer = match self.peers.get_mut(who) { None => return, Some(peer) => peer, }; - let message_hash = HashFor::::hash(&message.data); + let message_hash = HashFor::::hash(&message); trace!(target: "gossip", "Sending direct to {}: {:?}", who, message); peer.known_messages.insert(message_hash); - network.write_notification(who.clone(), message.engine_id, message.data); + network.write_notification(who.clone(), engine_id, message); } } @@ -513,7 +518,8 @@ mod tests { $consensus.messages.push(MessageEntry { message_hash: $hash, topic: $topic, - message: ConsensusMessage { data: $m, engine_id: [0, 0, 0, 0]}, + engine_id: [0, 0, 0, 0], + message: $m, sender: None, }); } @@ -588,13 +594,14 @@ mod tests { let mut consensus = ConsensusGossip::::new(); consensus.register_validator_internal([0, 0, 0, 0], Arc::new(AllowAll)); - let message = ConsensusMessage { data: vec![4, 5, 6], engine_id: [0, 0, 0, 0] }; + let engine_id = [0, 0, 0, 0]; + let message = vec![4, 5, 6]; let topic = HashFor::::hash(&[1,2,3]); - consensus.register_message(topic, message.clone()); + consensus.register_message(topic, engine_id, message.clone()); let mut stream = block_on_stream(consensus.messages_for([0, 0, 0, 0], topic)); - assert_eq!(stream.next(), Some(TopicNotification { message: message.data, sender: None })); + assert_eq!(stream.next(), Some(TopicNotification { message: message, sender: None })); } #[test] @@ -602,11 +609,11 @@ mod tests { let mut consensus = ConsensusGossip::::new(); let topic = [1; 32].into(); - let msg_a = ConsensusMessage { data: vec![1, 2, 3], engine_id: [0, 0, 0, 0] }; - let msg_b = ConsensusMessage { data: vec![4, 5, 6], engine_id: [0, 0, 0, 0] }; + let msg_a = vec![1, 2, 3]; + let msg_b = vec![4, 5, 6]; - consensus.register_message(topic, msg_a); - consensus.register_message(topic, msg_b); + consensus.register_message(topic, [0, 0, 0, 0], msg_a); + consensus.register_message(topic, [0, 0, 0, 0], msg_b); assert_eq!(consensus.messages.len(), 2); } @@ -616,17 +623,16 @@ mod tests { let mut consensus = ConsensusGossip::::new(); consensus.register_validator_internal([0, 0, 0, 0], Arc::new(AllowAll)); - let data = vec![4, 5, 6]; - let message = ConsensusMessage { data: data.clone(), engine_id: [0, 0, 0, 0] }; + let message = vec![4, 5, 6]; let topic = HashFor::::hash(&[1, 2, 3]); - consensus.register_message(topic, message.clone()); + consensus.register_message(topic, [0, 0, 0, 0], message.clone()); let mut stream1 = block_on_stream(consensus.messages_for([0, 0, 0, 0], topic)); let mut stream2 = block_on_stream(consensus.messages_for([0, 0, 0, 0], topic)); - assert_eq!(stream1.next(), Some(TopicNotification { message: data.clone(), sender: None })); - assert_eq!(stream2.next(), Some(TopicNotification { message: data, sender: None })); + assert_eq!(stream1.next(), Some(TopicNotification { message: message.clone(), sender: None })); + assert_eq!(stream2.next(), Some(TopicNotification { message, sender: None })); } #[test] @@ -635,11 +641,11 @@ mod tests { consensus.register_validator_internal([0, 0, 0, 0], Arc::new(AllowAll)); let topic = [1; 32].into(); - let msg_a = ConsensusMessage { data: vec![1, 2, 3], engine_id: [0, 0, 0, 0] }; - let msg_b = ConsensusMessage { data: vec![4, 5, 6], engine_id: [0, 0, 0, 1] }; + let msg_a = vec![1, 2, 3]; + let msg_b = vec![4, 5, 6]; - consensus.register_message(topic, msg_a); - consensus.register_message(topic, msg_b); + consensus.register_message(topic, [0, 0, 0, 0], msg_a); + consensus.register_message(topic, [0, 0, 0, 1], msg_b); let mut stream = block_on_stream(consensus.messages_for([0, 0, 0, 0], topic)); diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index d99dce2fd2cdd6c78411e2cf412e4227a71aeafa..465cb2632f34db81859f1e719fca17204a333a68 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Substrate network protocol" name = "sc-network" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" license = "GPL-3.0" authors = ["Parity Technologies "] edition = "2018" @@ -16,13 +16,13 @@ prost-build = "0.6.1" [dependencies] bitflags = "1.2.0" bytes = "0.5.0" -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } derive_more = "0.99.2" either = "1.5.3" erased-serde = "0.3.9" fnv = "1.0.6" -fork-tree = { version = "2.0.0-alpha.2", path = "../../utils/fork-tree" } -futures = "0.3.1" +fork-tree = { version = "2.0.0-alpha.5", path = "../../utils/fork-tree" } +futures = "0.3.4" futures_codec = "0.3.3" futures-timer = "3.0.1" wasm-timer = "0.2" @@ -35,24 +35,24 @@ nohash-hasher = "0.2.0" parking_lot = "0.10.0" prost = "0.6.1" rand = "0.7.2" -rustc-hex = "2.0.1" -sc-block-builder = { version = "0.8.0-alpha.2", path = "../block-builder" } -sc-client = { version = "0.8.0-alpha.2", path = "../" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sc-peerset = { version = "2.0.0-alpha.2", path = "../peerset" } +hex = "0.4.0" +sc-block-builder = { version = "0.8.0-alpha.5", path = "../block-builder" } +sc-client = { version = "0.8.0-alpha.5", path = "../" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sc-peerset = { version = "2.0.0-alpha.5", path = "../peerset" } pin-project = "0.4.6" serde = { version = "1.0.101", features = ["derive"] } serde_json = "1.0.41" slog = { version = "2.5.2", features = ["nested-values"] } slog_derive = "0.2.0" smallvec = "0.6.10" -sp-arithmetic = { version = "2.0.0-alpha.2", path = "../../primitives/arithmetic" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../primitives/consensus/common" } -sp-consensus-babe = { version = "0.8.0-alpha.2", path = "../../primitives/consensus/babe" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.8.0-alpha.2", path = "../../utils/prometheus" } +sp-arithmetic = { version = "2.0.0-alpha.5", path = "../../primitives/arithmetic" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/common" } +sp-consensus-babe = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/babe" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.8.0-alpha.5", path = "../../utils/prometheus" } thiserror = "1" unsigned-varint = { version = "0.3.1", features = ["futures", "futures-codec"] } void = "1.0.2" @@ -64,7 +64,7 @@ assert_matches = "1.3" env_logger = "0.7.0" quickcheck = "0.9.0" rand = "0.7.2" -sp-keyring = { version = "2.0.0-alpha.2", path = "../../primitives/keyring" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../../primitives/keyring" } sp-test-primitives = { version = "2.0.0-dev", path = "../../primitives/test-primitives" } substrate-test-runtime = { version = "2.0.0-dev", path = "../../test-utils/runtime" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client" } @@ -73,3 +73,6 @@ tempfile = "3.1.0" [features] default = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index ecce7d81e30cd62ed2856f03aed35eaa480ce215..ed5016642be8b3e47e27588d2a86f3a01ea98614 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -293,7 +293,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { fn inject_new_external_addr(&mut self, addr: &Multiaddr) { let new_addr = addr.clone() .with(Protocol::P2p(self.local_peer_id.clone().into())); - info!(target: "sub-libp2p", "Discovered new external address for our node: {}", new_addr); + info!(target: "sub-libp2p", "🔍 Discovered new external address for our node: {}", new_addr); NetworkBehaviour::inject_new_external_addr(&mut self.kademlia, addr) } diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index a5397a4e3e6263f2c6c33263b7002549cedcacae..23233ee9048fba85aae3cbcd4ab17791b9f46dd1 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -90,23 +90,57 @@ //! ## Substreams //! //! Once a connection has been established and uses multiplexing, substreams can be opened. When -//! a substream is open, the **multistream-select** protocol is used to negotiate which protocol to -//! use on that given substream. In practice, Substrate opens the following substreams: -//! -//! - We periodically open an ephemeral substream in order to ping the remote and check whether the -//! connection is still alive. Failure for the remote to reply leads to a disconnection. This uses -//! the libp2p ping protocol. -//! - We periodically open an ephemeral substream in order to ask information from the remote. This -//! is called [the `identify` protocol](https://github.com/libp2p/specs/tree/master/identify). -//! - We periodically open ephemeral substreams for Kademlia random walk queries. Each Kademlia -//! query is done in a new separate substream. This uses the -//! [standard libp2p Kademlia protocol](https://github.com/libp2p/specs/pull/108). -//! - We optionally keep a substream alive for all Substrate-based communications. The name of the -//! protocol negotiated is based on the *protocol ID* passed as part of the network configuration. -//! This protocol ID should be unique for each chain and prevents nodes from different chains from -//! connecting to each other. More information below. -//! -//! ## The Substrate substream +//! a substream is open, the **multistream-select** protocol is used to negotiate which protocol +//! to use on that given substream. +//! +//! Protocols that are specific to a certain chain have a `` in their name. This +//! "protocol ID" is defined in the chain specifications. For example, the protocol ID of Polkadot +//! is "dot". In the protocol names below, `` must be replaced with the corresponding +//! 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. +//! +//! Substrate uses the following standard libp2p protocols: +//! +//! - **`/ipfs/ping/1.0.0`**. We periodically open an ephemeral substream in order to ping the +//! remote and check whether the connection is still alive. Failure for the remote to reply leads +//! to a disconnection. +//! - **[`/ipfs/id/1.0.0`](https://github.com/libp2p/specs/tree/master/identify)**. We +//! periodically open an ephemeral substream in order to ask information from the remote. +//! - **[`/ipfs/kad/1.0.0`](https://github.com/libp2p/specs/pull/108)**. We periodically open +//! ephemeral substreams for Kademlia random walk queries. Each Kademlia query is done in a +//! separate substream. +//! +//! Additionally, Substrate uses the following non-libp2p-standard protocols: +//! +//! - **`/substrate//`** (where `` must be replaced with the +//! protocol ID of the targeted chain, and `` is a number between 2 and 6). For each +//! connection we optionally keep an additional substream for all Substrate-based communications alive. +//! This protocol is considered legacy, and is progressively being replaced with alternatives. +//! This is designated as "The legacy Substrate substream" in this documentation. See below for +//! more details. +//! - **`//sync/2`** is a request-response protocol (see below) that lets one perform +//! requests for information about blocks. Each request is the encoding of a `BlockRequest` and +//! each response is the encoding of a `BlockResponse`, as defined in the `api.v1.proto` file in +//! this source tree. +//! - **`//light/2`** is a request-response protocol (see below) that lets one perform +//! light-client-related requests for information about the state. Each request is the encoding of +//! a `light::Request` and each response is the encoding of a `light::Response`, as defined in the +//! `light.v1.proto` file in this source tree. +//! - **`//transactions/1`** is a notifications protocol (see below) where +//! transactions are pushed to other nodes. The handshake is empty on both sides. The message +//! format is a SCALE-encoded list of transactions, where each transaction is an opaque list of +//! bytes. +//! - **`//block-announces/1`** is a notifications protocol (see below) where +//! block announces are pushed to other nodes. The handshake is empty on both sides. The message +//! format is a SCALE-encoded tuple containing a block header followed with an opaque list of +//! bytes containing some data associated with this block announcement, e.g. a candidate message. +//! - Notifications protocols that are registered using the `register_notifications_protocol` +//! method. For example: `/paritytech/grandpa/1`. See below for more information. +//! +//! ## The legacy Substrate substream //! //! Substrate uses a component named the **peerset manager (PSM)**. Through the discovery //! mechanism, the PSM is aware of the nodes that are part of the network and decides which nodes @@ -119,8 +153,8 @@ //! Note that at the moment there is no mechanism in place to solve the issues that arise where the //! two sides of a connection open the unique substream simultaneously. In order to not run into //! issues, only the dialer of a connection is allowed to open the unique substream. When the -//! substream is closed, the entire connection is closed as well. This is a bug, and should be -//! fixed by improving the protocol. +//! substream is closed, the entire connection is closed as well. This is a bug that will be +//! resolved by deprecating the protocol entirely. //! //! Within the unique Substrate substream, messages encoded using //! [*parity-scale-codec*](https://github.com/paritytech/parity-scale-codec) are exchanged. @@ -137,9 +171,46 @@ //! substream open with is chosen, and the information is requested from it. //! - Gossiping. Used for example by grandpa. //! -//! It is intended that in the future each of these components gets more isolated, so that they -//! are free to open and close their own substreams, and so that syncing and light client requests -//! are able to communicate with nodes outside of the range of the PSM. +//! ## Request-response protocols +//! +//! A so-called request-response protocol is defined as follow: +//! +//! - When a substream is opened, the opening side sends a message whose content is +//! protocol-specific. The message must be prefixed with an +//! [LEB128-encoded number](https://en.wikipedia.org/wiki/LEB128) indicating its length. After the +//! message has been sent, the writing side is closed. +//! - The remote sends back the response prefixed with a LEB128-encoded length, and closes its +//! side as well. +//! +//! Each request is performed in a new separate substream. +//! +//! ## Notifications protocols +//! +//! A so-called notifications protocol is defined as follow: +//! +//! - When a substream is opened, the opening side sends a handshake message whose content is +//! protocol-specific. The handshake message must be prefixed with an +//! [LEB128-encoded number](https://en.wikipedia.org/wiki/LEB128) indicating its length. The +//! handshake message can be of length 0, in which case the sender has to send a single `0`. +//! - The receiver then either immediately closes the substream, or answers with its own +//! LEB128-prefixed protocol-specific handshake response. The message can be of length 0, in which +//! case a single `0` has to be sent back. The receiver is then encouraged to close its sending +//! side. +//! - Once the handshake has completed, the notifications protocol is unidirectional. Only the +//! node which initiated the substream can push notifications. If the remote wants to send +//! notifications as well, it has to open its own undirectional substream. +//! - Each notification must be prefixed with an LEB128-encoded length. The encoding of the +//! messages is specific to each protocol. +//! +//! The API of `sc-network` allows one to register user-defined notification protocols. +//! `sc-network` automatically tries to open a substream towards each node for which the legacy +//! Substream substream is open. The handshake is then performed automatically. +//! +//! For example, the `sc-finality-grandpa` crate registers the `/paritytech/grandpa/1` +//! notifications protocol. +//! +//! At the moment, for backwards-compatibility, notification protocols are tied to the legacy +//! Substrate substream. In the future, though, it will no longer be the case. //! //! # Usage //! @@ -183,13 +254,6 @@ pub use libp2p::{Multiaddr, PeerId}; #[doc(inline)] pub use libp2p::multiaddr; -// Note: these re-exports shouldn't be part of the public API of the crate and will be removed in -// the future. -#[doc(hidden)] -pub use protocol::message; -#[doc(hidden)] -pub use protocol::message::Status as StatusMessage; - pub use sc_peerset::ReputationChange; /// Extension trait for `NetworkBehaviour` that also accepts discovering nodes. diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 2c0cb9df2e2001a5ff146e02a98356b2fa0e81a2..55bc40a95047e8d3b6829f2d287c6a96920392d4 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -23,7 +23,10 @@ use libp2p::{Multiaddr, PeerId}; use libp2p::core::{ConnectedPoint, nodes::listeners::ListenerId}; use libp2p::swarm::{ProtocolsHandler, IntoProtocolsHandler}; use libp2p::swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters}; -use sp_core::storage::{StorageKey, ChildInfo}; +use sp_core::{ + storage::{StorageKey, ChildInfo}, + hexdisplay::HexDisplay +}; use sp_consensus::{ BlockOrigin, block_validation::BlockAnnounceValidator, @@ -36,13 +39,12 @@ use sp_runtime::traits::{ }; use sp_arithmetic::traits::SaturatedConversion; use message::{BlockAnnounce, BlockAttributes, Direction, FromBlock, Message, RequestId}; -use message::generic::Message as GenericMessage; +use message::generic::{Message as GenericMessage, ConsensusMessage}; use light_dispatch::{LightDispatch, LightDispatchNetwork, RequestData}; -use prometheus_endpoint::{Registry, Gauge, register, PrometheusError, U64}; +use prometheus_endpoint::{Registry, Gauge, GaugeVec, PrometheusError, Opts, register, U64}; use sync::{ChainSync, SyncState}; use crate::service::{TransactionPool, ExHashT}; use crate::config::{BoxFinalityProofRequestBuilder, Roles}; -use rustc_hex::ToHex; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap, HashSet}; use std::sync::Arc; @@ -142,92 +144,50 @@ struct Metrics { peers: Gauge, queued_blocks: Gauge, fork_targets: Gauge, - finality_proofs_pending: Gauge, - finality_proofs_active: Gauge, - finality_proofs_failed: Gauge, - finality_proofs_importing: Gauge, - justifications_pending: Gauge, - justifications_active: Gauge, - justifications_failed: Gauge, - justifications_importing: Gauge + finality_proofs: GaugeVec, + justifications: GaugeVec, } impl Metrics { fn register(r: &Registry) -> Result { Ok(Metrics { handshaking_peers: { - let g = Gauge::new("sync_handshaking_peers", "number of newly connected peers")?; + let g = Gauge::new("sync_handshaking_peers", "Number of newly connected peers")?; register(g, r)? }, obsolete_requests: { - let g = Gauge::new("sync_obsolete_requests", "total number of obsolete requests")?; + let g = Gauge::new("sync_obsolete_requests", "Number of obsolete requests")?; register(g, r)? }, peers: { - let g = Gauge::new("sync_peers", "number of peers we sync with")?; + let g = Gauge::new("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("sync_queued_blocks", "Number of blocks in import queue")?; register(g, r)? }, fork_targets: { - let g = Gauge::new("sync_fork_targets", "fork sync targets")?; - register(g, r)? - }, - justifications_pending: { - let g = Gauge::new( - "sync_extra_justifications_pending", - "number of pending extra justifications requests" - )?; - register(g, r)? - }, - justifications_active: { - let g = Gauge::new( - "sync_extra_justifications_active", - "number of active extra justifications requests" - )?; - register(g, r)? - }, - justifications_failed: { - let g = Gauge::new( - "sync_extra_justifications_failed", - "number of failed extra justifications requests" - )?; + let g = Gauge::new("sync_fork_targets", "Number of fork sync targets")?; register(g, r)? }, - justifications_importing: { - let g = Gauge::new( - "sync_extra_justifications_importing", - "number of importing extra justifications requests" + justifications: { + let g = GaugeVec::new( + Opts::new( + "sync_extra_justifications", + "Number of extra justifications requests" + ), + &["status"], )?; register(g, r)? }, - finality_proofs_pending: { - let g = Gauge::new( - "sync_extra_finality_proofs_pending", - "number of pending extra finality proof requests" - )?; - register(g, r)? - }, - finality_proofs_active: { - let g = Gauge::new( - "sync_extra_finality_proofs_active", - "number of active extra finality proof requests" - )?; - register(g, r)? - }, - finality_proofs_failed: { - let g = Gauge::new( - "sync_extra_finality_proofs_failed", - "number of failed extra finality proof requests" - )?; - register(g, r)? - }, - finality_proofs_importing: { - let g = Gauge::new( - "sync_extra_finality_proofs_importing", - "number of importing extra finality proof requests" + finality_proofs: { + let g = GaugeVec::new( + Opts::new( + "sync_extra_finality_proofs", + "Number of extra finality proof requests", + ), + &["status"], )?; register(g, r)? }, @@ -261,10 +221,16 @@ pub struct Protocol { behaviour: GenericProto, /// For each legacy gossiping engine ID, the corresponding new protocol name. protocol_name_by_engine: HashMap>, - /// For each protocol name, the legacy gossiping engine ID. - protocol_engine_by_name: HashMap, ConsensusEngineId>, + /// For each protocol name, the legacy equivalent. + legacy_equiv_by_name: HashMap, Fallback>, + /// Name of the protocol used for transactions. + transactions_protocol: Cow<'static, [u8]>, + /// Name of the protocol used for block announces. + block_announces_protocol: Cow<'static, [u8]>, /// Prometheus metrics. metrics: Option, + /// The `PeerId`'s of all boot nodes. + boot_node_ids: Arc>, } #[derive(Default)] @@ -462,6 +428,17 @@ impl Default for ProtocolConfig { } } +/// Fallback mechanism to use to send a notification if no substream is open. +#[derive(Debug, Clone, PartialEq, Eq)] +enum Fallback { + /// Use a `Message::Consensus` with the given engine ID. + Consensus(ConsensusEngineId), + /// The message is the bytes encoding of a `Transactions` (which is itself defined as a `Vec`). + Transactions, + /// The message is the bytes encoding of a `BlockAnnounce`. + BlockAnnounce, +} + impl Protocol { /// Create a new instance. pub fn new( @@ -474,7 +451,8 @@ impl Protocol { protocol_id: ProtocolId, peerset_config: sc_peerset::PeersetConfig, block_announce_validator: Box + Send>, - metrics_registry: Option<&Registry> + metrics_registry: Option<&Registry>, + boot_node_ids: Arc>, ) -> error::Result<(Protocol, sc_peerset::PeersetHandle)> { let info = chain.info(); let sync = ChainSync::new( @@ -497,7 +475,27 @@ impl Protocol { let (peerset, peerset_handle) = sc_peerset::Peerset::from_config(peerset_config); let versions = &((MIN_VERSION as u8)..=(CURRENT_VERSION as u8)).collect::>(); - let behaviour = GenericProto::new(protocol_id, versions, peerset); + let mut behaviour = GenericProto::new(protocol_id.clone(), versions, peerset); + + let mut legacy_equiv_by_name = HashMap::new(); + + let transactions_protocol: Cow<'static, [u8]> = Cow::from({ + let mut proto = b"/".to_vec(); + proto.extend(protocol_id.as_bytes()); + proto.extend(b"/transactions/1"); + proto + }); + behaviour.register_notif_protocol(transactions_protocol.clone(), Vec::new()); + legacy_equiv_by_name.insert(transactions_protocol.clone(), Fallback::Transactions); + + let block_announces_protocol: Cow<'static, [u8]> = Cow::from({ + let mut proto = b"/".to_vec(); + proto.extend(protocol_id.as_bytes()); + proto.extend(b"/block-announces/1"); + proto + }); + behaviour.register_notif_protocol(block_announces_protocol.clone(), Vec::new()); + legacy_equiv_by_name.insert(block_announces_protocol.clone(), Fallback::BlockAnnounce); let protocol = Protocol { tick_timeout: Box::pin(interval(TICK_TIMEOUT)), @@ -518,12 +516,15 @@ impl Protocol { peerset_handle: peerset_handle.clone(), behaviour, protocol_name_by_engine: HashMap::new(), - protocol_engine_by_name: HashMap::new(), + legacy_equiv_by_name, + transactions_protocol, + block_announces_protocol, metrics: if let Some(r) = metrics_registry { Some(Metrics::register(r)?) } else { None - } + }, + boot_node_ids, }; Ok((protocol, peerset_handle)) @@ -598,6 +599,11 @@ impl Protocol { self.sync.status().queued_blocks } + /// Number of processed blocks. + pub fn num_processed_blocks(&self) -> usize { + self.sync.num_processed_blocks() + } + /// Number of active sync requests. pub fn num_sync_requests(&self) -> usize { self.sync.num_sync_requests() @@ -762,12 +768,18 @@ impl Protocol { ); } - fn send_message(&mut self, who: &PeerId, message: Message) { + fn send_message( + &mut self, + who: &PeerId, + message: Option<(Cow<'static, [u8]>, Vec)>, + legacy: Message, + ) { send_message::( &mut self.behaviour, &mut self.context_data.stats, who, message, + legacy, ); } @@ -824,11 +836,7 @@ impl Protocol { } } - fn on_block_request( - &mut self, - peer: PeerId, - request: message::BlockRequest - ) { + fn on_block_request(&mut self, peer: PeerId, request: message::BlockRequest) { trace!(target: "sync", "BlockRequest {} from {}: from {:?} to {:?} max {:?} for {:?}", request.id, peer, @@ -905,7 +913,7 @@ impl Protocol { blocks: blocks, }; trace!(target: "sync", "Sending BlockResponse with {} blocks", response.blocks.len()); - self.send_message(&peer, GenericMessage::BlockResponse(response)) + self.send_message(&peer, None, GenericMessage::BlockResponse(response)) } /// Adjusts the reputation of a node. @@ -1049,6 +1057,17 @@ impl Protocol { ); self.peerset_handle.report_peer(who.clone(), rep::GENESIS_MISMATCH); self.behaviour.disconnect_peer(&who); + + if self.boot_node_ids.contains(&who) { + error!( + target: "sync", + "Bootnode with peer id `{}` is on a different chain (our genesis: {} theirs: {})", + who, + self.genesis_hash, + status.genesis_hash, + ); + } + return CustomMessageOutcome::None; } if status.version < MIN_VERSION && CURRENT_VERSION < status.min_supported_version { @@ -1152,10 +1171,15 @@ impl Protocol { &mut self, target: PeerId, engine_id: ConsensusEngineId, - message: impl Into> + message: impl Into>, ) { if let Some(protocol_name) = self.protocol_name_by_engine.get(&engine_id) { - self.behaviour.write_notification(&target, engine_id, protocol_name.clone(), message); + let message = message.into(); + let fallback = GenericMessage::<(), (), (), ()>::Consensus(ConsensusMessage { + engine_id, + data: message.clone(), + }).encode(); + self.behaviour.write_notification(&target, protocol_name.clone(), message, fallback); } else { error!( target: "sub-libp2p", @@ -1178,8 +1202,8 @@ impl Protocol { if self.protocol_name_by_engine.insert(engine_id, protocol_name.clone()).is_some() { error!(target: "sub-libp2p", "Notifications protocol already registered: {:?}", protocol_name); } else { - self.behaviour.register_notif_protocol(protocol_name.clone(), engine_id, Vec::new()); - self.protocol_engine_by_name.insert(protocol_name, engine_id); + self.behaviour.register_notif_protocol(protocol_name.clone(), Vec::new()); + self.legacy_equiv_by_name.insert(protocol_name, Fallback::Consensus(engine_id)); } // Registering a protocol while we already have open connections isn't great, but for now @@ -1249,7 +1273,7 @@ impl Protocol { fn do_propagate_extrinsics( &mut self, extrinsics: &[(H, B::Extrinsic)], - ) -> HashMap> { + ) -> HashMap> { let mut propagated_to = HashMap::new(); for (who, peer) in self.context_data.peers.iter_mut() { // never send extrinsics to the light node @@ -1271,10 +1295,12 @@ impl Protocol { .push(who.to_base58()); } trace!(target: "sync", "Sending {} transactions to {}", to_send.len(), who); + let encoded = to_send.encode(); send_message:: ( &mut self.behaviour, &mut self.context_data.stats, &who, + Some((self.transactions_protocol.clone(), encoded)), GenericMessage::Transactions(to_send) ) } @@ -1329,7 +1355,7 @@ impl Protocol { trace!(target: "sync", "Announcing block {:?} to {}", hash, who); let inserted = peer.known_blocks.insert(hash); if inserted || force { - let message: Message = GenericMessage::BlockAnnounce(message::BlockAnnounce { + let message = message::BlockAnnounce { header: header.clone(), state: if peer.info.protocol_version >= 4 { if is_best { @@ -1345,13 +1371,16 @@ impl Protocol { } else { None }, - }); + }; + + let encoded = message.encode(); send_message:: ( &mut self.behaviour, &mut self.context_data.stats, &who, - message, + Some((self.block_announces_protocol.clone(), encoded)), + Message::::BlockAnnounce(message), ) } } @@ -1370,10 +1399,14 @@ impl Protocol { chain_status: Vec::new(), // TODO: find a way to make this backwards-compatible }; - self.send_message(&who, GenericMessage::Status(status)) + self.send_message(&who, None, GenericMessage::Status(status)) } - fn on_block_announce(&mut self, who: PeerId, announce: BlockAnnounce) -> CustomMessageOutcome { + fn on_block_announce( + &mut self, + who: PeerId, + announce: BlockAnnounce, + ) -> CustomMessageOutcome { let hash = announce.header.hash(); if let Some(ref mut peer) = self.context_data.peers.get_mut(&who) { peer.known_blocks.insert(hash.clone()); @@ -1434,20 +1467,11 @@ impl Protocol { } } - /// Call this when a block has been imported in the import queue and we should announce it on - /// the network. - pub fn on_block_imported(&mut self, header: &B::Header, data: Vec, is_best: bool) { + /// Call this when a block has been imported in the import queue + pub fn on_block_imported(&mut self, header: &B::Header, is_best: bool) { if is_best { self.sync.update_chain_info(header); } - - // blocks are not announced by light clients - if self.config.roles.is_light() { - return; - } - - // send out block announcements - self.send_announcement(header, data, is_best, false); } /// Call this when a block has been finalized. The sync layer may have some additional @@ -1488,6 +1512,7 @@ impl Protocol { self.send_message( &who, + None, GenericMessage::RemoteCallResponse(message::RemoteCallResponse { id: request.id, proof, @@ -1590,11 +1615,11 @@ impl Protocol { } let keys_str = || match request.keys.len() { - 1 => request.keys[0].to_hex::(), + 1 => HexDisplay::from(&request.keys[0]).to_string(), _ => format!( "{}..{}", - request.keys[0].to_hex::(), - request.keys[request.keys.len() - 1].to_hex::(), + HexDisplay::from(&request.keys[0]), + HexDisplay::from(&request.keys[request.keys.len() - 1]), ), }; @@ -1618,6 +1643,7 @@ impl Protocol { }; self.send_message( &who, + None, GenericMessage::RemoteReadResponse(message::RemoteReadResponse { id: request.id, proof, @@ -1638,16 +1664,16 @@ impl Protocol { } let keys_str = || match request.keys.len() { - 1 => request.keys[0].to_hex::(), + 1 => HexDisplay::from(&request.keys[0]).to_string(), _ => format!( "{}..{}", - request.keys[0].to_hex::(), - request.keys[request.keys.len() - 1].to_hex::(), + HexDisplay::from(&request.keys[0]), + HexDisplay::from(&request.keys[request.keys.len() - 1]), ), }; trace!(target: "sync", "Remote read child request {} from {} ({} {} at {})", - request.id, who, request.storage_key.to_hex::(), keys_str(), request.block); + request.id, who, HexDisplay::from(&request.storage_key), keys_str(), request.block); let proof = if let Some(child_info) = ChildInfo::resolve_child_info(request.child_type, &request.child_info[..]) { match self.context_data.chain.read_child_proof( &BlockId::Hash(request.block), @@ -1660,7 +1686,7 @@ impl Protocol { trace!(target: "sync", "Remote read child request {} from {} ({} {} at {}) failed with: {}", request.id, who, - request.storage_key.to_hex::(), + HexDisplay::from(&request.storage_key), keys_str(), request.block, error @@ -1672,7 +1698,7 @@ impl Protocol { trace!(target: "sync", "Remote read child request {} from {} ({} {} at {}) failed with: {}", request.id, who, - request.storage_key.to_hex::(), + HexDisplay::from(&request.storage_key), keys_str(), request.block, "invalid child info and type", @@ -1682,6 +1708,7 @@ impl Protocol { }; self.send_message( &who, + None, GenericMessage::RemoteReadResponse(message::RemoteReadResponse { id: request.id, proof, @@ -1722,6 +1749,7 @@ impl Protocol { }; self.send_message( &who, + None, GenericMessage::RemoteHeaderResponse(message::RemoteHeaderResponse { id: request.id, header, @@ -1751,9 +1779,9 @@ impl Protocol { request.id, who, if let Some(sk) = request.storage_key.as_ref() { - format!("{} : {}", sk.to_hex::(), request.key.to_hex::()) + format!("{} : {}", HexDisplay::from(sk), HexDisplay::from(&request.key)) } else { - request.key.to_hex::() + HexDisplay::from(&request.key).to_string() }, request.first, request.last @@ -1774,9 +1802,9 @@ impl Protocol { request.id, who, if let Some(sk) = storage_key { - format!("{} : {}", sk.0.to_hex::(), key.0.to_hex::()) + format!("{} : {}", HexDisplay::from(&sk.0), HexDisplay::from(&key.0)) } else { - key.0.to_hex::() + HexDisplay::from(&key.0).to_string() }, request.first, request.last, @@ -1792,6 +1820,7 @@ impl Protocol { }; self.send_message( &who, + None, GenericMessage::RemoteChangesResponse(message::RemoteChangesResponse { id: request.id, max: proof.max_block, @@ -1842,6 +1871,7 @@ impl Protocol { }; self.send_message( &who, + None, GenericMessage::FinalityProofResponse(message::FinalityProofResponse { id: 0, block: request.block, @@ -1917,15 +1947,23 @@ impl Protocol { metrics.fork_targets.set(m.fork_targets.into()); metrics.queued_blocks.set(m.queued_blocks.into()); - metrics.justifications_pending.set(m.justifications.pending_requests.into()); - metrics.justifications_active.set(m.justifications.active_requests.into()); - metrics.justifications_failed.set(m.justifications.failed_requests.into()); - metrics.justifications_importing.set(m.justifications.importing_requests.into()); - - metrics.finality_proofs_pending.set(m.finality_proofs.pending_requests.into()); - metrics.finality_proofs_active.set(m.finality_proofs.active_requests.into()); - metrics.finality_proofs_failed.set(m.finality_proofs.failed_requests.into()); - metrics.finality_proofs_importing.set(m.finality_proofs.importing_requests.into()); + metrics.justifications.with_label_values(&["pending"]) + .set(m.justifications.pending_requests.into()); + metrics.justifications.with_label_values(&["active"]) + .set(m.justifications.active_requests.into()); + metrics.justifications.with_label_values(&["failed"]) + .set(m.justifications.failed_requests.into()); + metrics.justifications.with_label_values(&["importing"]) + .set(m.justifications.importing_requests.into()); + + metrics.finality_proofs.with_label_values(&["pending"]) + .set(m.finality_proofs.pending_requests.into()); + metrics.finality_proofs.with_label_values(&["active"]) + .set(m.finality_proofs.active_requests.into()); + metrics.finality_proofs.with_label_values(&["failed"]) + .set(m.finality_proofs.failed_requests.into()); + metrics.finality_proofs.with_label_values(&["importing"]) + .set(m.finality_proofs.importing_requests.into()); } } } @@ -1963,20 +2001,25 @@ fn send_request( peer.block_request = Some((Instant::now(), r.clone())); } } - send_message::(behaviour, stats, who, message) + send_message::(behaviour, stats, who, None, message) } fn send_message( behaviour: &mut GenericProto, stats: &mut HashMap<&'static str, PacketStats>, who: &PeerId, - message: Message, + message: Option<(Cow<'static, [u8]>, Vec)>, + legacy_message: Message, ) { - let encoded = message.encode(); - let mut stats = stats.entry(message.id()).or_default(); + let encoded = legacy_message.encode(); + let mut stats = stats.entry(legacy_message.id()).or_default(); stats.bytes_out += encoded.len() as u64; stats.count_out += 1; - behaviour.send_packet(who, encoded); + if let Some((proto, msg)) = message { + behaviour.write_notification(who, proto, msg, encoded); + } else { + behaviour.send_packet(who, encoded); + } } impl NetworkBehaviour for Protocol { @@ -2073,8 +2116,39 @@ impl NetworkBehaviour for Protocol { GenericProtoOut::CustomProtocolClosed { peer_id, .. } => { self.on_peer_disconnected(peer_id.clone()) }, - GenericProtoOut::CustomMessage { peer_id, message } => + GenericProtoOut::LegacyMessage { peer_id, message } => self.on_custom_message(peer_id, message), + GenericProtoOut::Notification { peer_id, protocol_name, message } => + match self.legacy_equiv_by_name.get(&protocol_name) { + Some(Fallback::Consensus(engine_id)) => { + CustomMessageOutcome::NotificationsReceived { + remote: peer_id, + messages: vec![(*engine_id, message.freeze())], + } + } + Some(Fallback::Transactions) => { + if let Ok(m) = message::Transactions::decode(&mut message.as_ref()) { + self.on_extrinsics(peer_id, m); + } else { + warn!(target: "sub-libp2p", "Failed to decode transactions list"); + } + CustomMessageOutcome::None + } + Some(Fallback::BlockAnnounce) => { + if let Ok(announce) = message::BlockAnnounce::decode(&mut message.as_ref()) { + let outcome = self.on_block_announce(peer_id.clone(), announce); + self.update_peer_info(&peer_id); + outcome + } else { + warn!(target: "sub-libp2p", "Failed to decode block announce"); + CustomMessageOutcome::None + } + } + None => { + error!(target: "sub-libp2p", "Received notification from unknown protocol {:?}", protocol_name); + CustomMessageOutcome::None + } + } GenericProtoOut::Clogged { peer_id, messages } => { debug!(target: "sync", "{} clogging messages:", messages.len()); for msg in messages.into_iter().take(5) { @@ -2178,7 +2252,8 @@ mod tests { reserved_nodes: Vec::new(), }, Box::new(DefaultBlockAnnounceValidator::new(client.clone())), - None + None, + Default::default(), ).unwrap(); let dummy_peer_id = PeerId::random(); diff --git a/client/network/src/protocol/generic_proto/behaviour.rs b/client/network/src/protocol/generic_proto/behaviour.rs index 727415baaf5bcd9a4b06ca847d9aeb6968f78ab1..63625f1c9ffe1038c17537a589ccb5e36bf91847 100644 --- a/client/network/src/protocol/generic_proto/behaviour.rs +++ b/client/network/src/protocol/generic_proto/behaviour.rs @@ -15,12 +15,10 @@ // along with Substrate. If not, see . use crate::{DiscoveryNetBehaviour, config::ProtocolId}; -use crate::protocol::message::generic::{Message as GenericMessage, ConsensusMessage}; use crate::protocol::generic_proto::handler::{NotifsHandlerProto, NotifsHandlerOut, NotifsHandlerIn}; use crate::protocol::generic_proto::upgrade::RegisteredProtocol; use bytes::BytesMut; -use codec::Encode as _; use fnv::FnvHashMap; use futures::prelude::*; use libp2p::core::{ConnectedPoint, Multiaddr, PeerId}; @@ -28,10 +26,9 @@ use libp2p::swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters}; use log::{debug, error, trace, warn}; use rand::distributions::{Distribution as _, Uniform}; use smallvec::SmallVec; -use sp_runtime::ConsensusEngineId; -use std::{borrow::Cow, collections::hash_map::Entry, cmp}; -use std::{error, mem, pin::Pin, str, time::Duration}; use std::task::{Context, Poll}; +use std::{borrow::Cow, cmp, collections::hash_map::Entry}; +use std::{error, mem, pin::Pin, str, time::Duration}; use wasm_timer::Instant; /// Network behaviour that handles opening substreams for custom protocols with other nodes. @@ -84,7 +81,7 @@ pub struct GenericProto { legacy_protocol: RegisteredProtocol, /// Notification protocols. Entries are only ever added and not removed. - notif_protocols: Vec<(Cow<'static, [u8]>, ConsensusEngineId, Vec)>, + notif_protocols: Vec<(Cow<'static, [u8]>, Vec)>, /// Receiver for instructions about who to connect to or disconnect from. peerset: sc_peerset::Peerset, @@ -238,12 +235,22 @@ pub enum GenericProtoOut { reason: Cow<'static, str>, }, + /// Receives a message on the legacy substream. + LegacyMessage { + /// Id of the peer the message came from. + peer_id: PeerId, + /// Message that has been received. + message: BytesMut, + }, + /// Receives a message on a custom protocol substream. /// /// Also concerns received notifications for the notifications API. - CustomMessage { + Notification { /// Id of the peer the message came from. peer_id: PeerId, + /// Engine corresponding to the message. + protocol_name: Cow<'static, [u8]>, /// Message that has been received. message: BytesMut, }, @@ -285,10 +292,9 @@ impl GenericProto { pub fn register_notif_protocol( &mut self, protocol_name: impl Into>, - engine_id: ConsensusEngineId, handshake_msg: impl Into> ) { - self.notif_protocols.push((protocol_name.into(), engine_id, handshake_msg.into())); + self.notif_protocols.push((protocol_name.into(), handshake_msg.into())); } /// Returns the number of discovered nodes that we keep in memory. @@ -406,14 +412,15 @@ impl GenericProto { /// Also note that even if we have a valid open substream, it may in fact be already closed /// without us knowing, in which case the packet will not be received. /// - /// > **Note**: Ideally the `engine_id` parameter wouldn't be necessary. See the documentation - /// > of [`NotifsHandlerIn`] for more information. + /// The `fallback` parameter is used for backwards-compatibility reason if the remote doesn't + /// support our protocol. One needs to pass the equivalent of what would have been passed + /// with `send_packet`. pub fn write_notification( &mut self, target: &PeerId, - engine_id: ConsensusEngineId, protocol_name: Cow<'static, [u8]>, message: impl Into>, + encoded_fallback_message: Vec, ) { if !self.is_open(target) { return; @@ -421,7 +428,7 @@ impl GenericProto { trace!( target: "sub-libp2p", - "External API => Notification for {:?} with protocol {:?}", + "External API => Notification({:?}, {:?})", target, str::from_utf8(&protocol_name) ); @@ -431,7 +438,7 @@ impl GenericProto { peer_id: target.clone(), event: NotifsHandlerIn::SendNotification { message: message.into(), - engine_id, + encoded_fallback_message, protocol_name, }, }); @@ -999,7 +1006,7 @@ impl NetworkBehaviour for GenericProto { debug_assert!(self.is_open(&source)); trace!(target: "sub-libp2p", "Handler({:?}) => Message", source); trace!(target: "sub-libp2p", "External API <= Message({:?})", source); - let event = GenericProtoOut::CustomMessage { + let event = GenericProtoOut::LegacyMessage { peer_id: source, message, }; @@ -1007,7 +1014,7 @@ impl NetworkBehaviour for GenericProto { self.events.push(NetworkBehaviourAction::GenerateEvent(event)); } - NotifsHandlerOut::Notification { protocol_name, engine_id, message } => { + NotifsHandlerOut::Notification { protocol_name, message } => { debug_assert!(self.is_open(&source)); trace!( target: "sub-libp2p", @@ -1015,18 +1022,11 @@ impl NetworkBehaviour for GenericProto { source, str::from_utf8(&protocol_name) ); - trace!(target: "sub-libp2p", "External API <= Message({:?})", source); - let event = GenericProtoOut::CustomMessage { + trace!(target: "sub-libp2p", "External API <= Message({:?}, {:?})", protocol_name, source); + let event = GenericProtoOut::Notification { peer_id: source, - message: { - let message = GenericMessage::<(), (), (), ()>::Consensus(ConsensusMessage { - engine_id, - data: message.to_vec(), - }); - - // Note that we clone `message` here. - From::from(&message.encode()[..]) - }, + protocol_name, + message, }; self.events.push(NetworkBehaviourAction::GenerateEvent(event)); diff --git a/client/network/src/protocol/generic_proto/handler/group.rs b/client/network/src/protocol/generic_proto/handler/group.rs index d6d9919d3e14df0c2261731cddb0811aa291f920..6b23263b14c5fe8637186c68d39bcfb3e44afe13 100644 --- a/client/network/src/protocol/generic_proto/handler/group.rs +++ b/client/network/src/protocol/generic_proto/handler/group.rs @@ -51,10 +51,8 @@ use crate::protocol::generic_proto::{ handler::notif_out::{NotifsOutHandlerProto, NotifsOutHandler, NotifsOutHandlerIn, NotifsOutHandlerOut}, upgrade::{NotificationsIn, NotificationsOut, NotificationsHandshakeError, RegisteredProtocol, UpgradeCollec}, }; -use crate::protocol::message::generic::{Message as GenericMessage, ConsensusMessage}; use bytes::BytesMut; -use codec::Encode as _; use libp2p::core::{either::{EitherError, EitherOutput}, ConnectedPoint, PeerId}; use libp2p::core::upgrade::{EitherUpgrade, UpgradeError, SelectUpgrade, InboundUpgrade, OutboundUpgrade}; use libp2p::swarm::{ @@ -65,9 +63,8 @@ use libp2p::swarm::{ SubstreamProtocol, NegotiatedSubstream, }; -use log::error; -use sp_runtime::ConsensusEngineId; -use std::{borrow::Cow, error, io, task::{Context, Poll}}; +use log::{debug, error}; +use std::{borrow::Cow, error, io, str, task::{Context, Poll}}; /// Implements the `IntoProtocolsHandler` trait of libp2p. /// @@ -78,10 +75,10 @@ use std::{borrow::Cow, error, io, task::{Context, Poll}}; /// See the documentation at the module level for more information. pub struct NotifsHandlerProto { /// Prototypes for handlers for inbound substreams. - in_handlers: Vec<(NotifsInHandlerProto, ConsensusEngineId)>, + in_handlers: Vec, /// Prototypes for handlers for outbound substreams. - out_handlers: Vec<(NotifsOutHandlerProto, ConsensusEngineId)>, + out_handlers: Vec, /// Prototype for handler for backwards-compatibility. legacy: LegacyProtoHandlerProto, @@ -92,10 +89,10 @@ pub struct NotifsHandlerProto { /// See the documentation at the module level for more information. pub struct NotifsHandler { /// Handlers for inbound substreams. - in_handlers: Vec<(NotifsInHandler, ConsensusEngineId)>, + in_handlers: Vec, /// Handlers for outbound substreams. - out_handlers: Vec<(NotifsOutHandler, ConsensusEngineId)>, + out_handlers: Vec, /// Handler for backwards-compatibility. legacy: LegacyProtoHandler, @@ -121,7 +118,7 @@ impl IntoProtocolsHandler for NotifsHandlerProto { fn inbound_protocol(&self) -> SelectUpgrade, RegisteredProtocol> { let in_handlers = self.in_handlers.iter() - .map(|(h, _)| h.inbound_protocol()) + .map(|h| h.inbound_protocol()) .collect::>(); SelectUpgrade::new(in_handlers, self.legacy.inbound_protocol()) @@ -131,11 +128,11 @@ impl IntoProtocolsHandler for NotifsHandlerProto { NotifsHandler { in_handlers: self.in_handlers .into_iter() - .map(|(p, e)| (p.into_handler(remote_peer_id, connected_point), e)) + .map(|p| p.into_handler(remote_peer_id, connected_point)) .collect(), out_handlers: self.out_handlers .into_iter() - .map(|(p, e)| (p.into_handler(remote_peer_id, connected_point), e)) + .map(|p| p.into_handler(remote_peer_id, connected_point)) .collect(), legacy: self.legacy.into_handler(remote_peer_id, connected_point), enabled: EnabledState::Initial, @@ -155,7 +152,8 @@ pub enum NotifsHandlerIn { /// Sends a message through the custom protocol substream. /// - /// > **Note**: This must **not** be an encoded `ConsensusMessage` message. + /// > **Note**: This must **not** be a `ConsensusMessage`, `Transactions`, or + /// > `BlockAnnounce` message. SendLegacy { /// The message to send. message: Vec, @@ -166,17 +164,13 @@ pub enum NotifsHandlerIn { /// Name of the protocol for the message. /// /// Must match one of the registered protocols. For backwards-compatibility reasons, if - /// the remote doesn't support this protocol, we use the legacy substream to send a - /// `ConsensusMessage` message. + /// the remote doesn't support this protocol, we use the legacy substream. protocol_name: Cow<'static, [u8]>, - /// The engine ID to use, in case we need to send this message over the legacy substream. + /// Message to send on the legacy substream if the protocol isn't available. /// - /// > **Note**: Ideally this field wouldn't be necessary, and we would deduce the engine - /// > ID from the existing handlers. However, it is possible (especially in test - /// > situations) that we open connections before all the notification protocols - /// > have been registered, in which case we always rely on the legacy substream. - engine_id: ConsensusEngineId, + /// This corresponds to what you would have sent with `SendLegacy`. + encoded_fallback_message: Vec, /// The message to send. message: Vec, @@ -206,17 +200,10 @@ pub enum NotifsHandlerOut { /// Received a message on a custom protocol substream. Notification { - /// Engine corresponding to the message. + /// Name of the protocol of the message. protocol_name: Cow<'static, [u8]>, - /// For legacy reasons, the name to use if we had received the message from the legacy - /// substream. - engine_id: ConsensusEngineId, - /// Message that has been received. - /// - /// If `protocol_name` is `None`, this decodes to a `Message`. If `protocol_name` is `Some`, - /// this is directly a gossiping message. message: BytesMut, }, @@ -238,12 +225,12 @@ pub enum NotifsHandlerOut { impl NotifsHandlerProto { /// Builds a new handler. - pub fn new(legacy: RegisteredProtocol, list: impl Into, ConsensusEngineId, Vec)>>) -> Self { + pub fn new(legacy: RegisteredProtocol, list: impl Into, Vec)>>) -> Self { let list = list.into(); NotifsHandlerProto { - in_handlers: list.clone().into_iter().map(|(p, e, _)| (NotifsInHandlerProto::new(p), e)).collect(), - out_handlers: list.clone().into_iter().map(|(p, e, _)| (NotifsOutHandlerProto::new(p), e)).collect(), + in_handlers: list.clone().into_iter().map(|(p, _)| NotifsInHandlerProto::new(p)).collect(), + out_handlers: list.clone().into_iter().map(|(p, _)| NotifsOutHandlerProto::new(p)).collect(), legacy: LegacyProtoHandlerProto::new(legacy), } } @@ -266,7 +253,7 @@ impl ProtocolsHandler for NotifsHandler { fn listen_protocol(&self) -> SubstreamProtocol { let in_handlers = self.in_handlers.iter() - .map(|h| h.0.listen_protocol().into_upgrade().1) + .map(|h| h.listen_protocol().into_upgrade().1) .collect::>(); let proto = SelectUpgrade::new(in_handlers, self.legacy.listen_protocol().into_upgrade().1); @@ -279,7 +266,7 @@ impl ProtocolsHandler for NotifsHandler { ) { match out { EitherOutput::First((out, num)) => - self.in_handlers[num].0.inject_fully_negotiated_inbound(out), + self.in_handlers[num].inject_fully_negotiated_inbound(out), EitherOutput::Second(out) => self.legacy.inject_fully_negotiated_inbound(out), } @@ -292,7 +279,7 @@ impl ProtocolsHandler for NotifsHandler { ) { match (out, num) { (EitherOutput::First(out), Some(num)) => - self.out_handlers[num].0.inject_fully_negotiated_outbound(out, ()), + self.out_handlers[num].inject_fully_negotiated_outbound(out, ()), (EitherOutput::Second(out), None) => self.legacy.inject_fully_negotiated_outbound(out, ()), _ => error!("inject_fully_negotiated_outbound called with wrong parameters"), @@ -302,54 +289,53 @@ impl ProtocolsHandler for NotifsHandler { fn inject_event(&mut self, message: NotifsHandlerIn) { match message { NotifsHandlerIn::Enable => { + if let EnabledState::Enabled = self.enabled { + debug!("enabling already-enabled handler"); + } self.enabled = EnabledState::Enabled; self.legacy.inject_event(LegacyProtoHandlerIn::Enable); - for (handler, _) in &mut self.out_handlers { + for handler in &mut self.out_handlers { handler.inject_event(NotifsOutHandlerIn::Enable { initial_message: vec![] }); } for num in self.pending_in.drain(..) { - self.in_handlers[num].0.inject_event(NotifsInHandlerIn::Accept(vec![])); + self.in_handlers[num].inject_event(NotifsInHandlerIn::Accept(vec![])); } }, NotifsHandlerIn::Disable => { + if let EnabledState::Disabled = self.enabled { + debug!("disabling already-disabled handler"); + } self.legacy.inject_event(LegacyProtoHandlerIn::Disable); // The notifications protocols start in the disabled state. If we were in the // "Initial" state, then we shouldn't disable the notifications protocols again. if self.enabled != EnabledState::Initial { - for (handler, _) in &mut self.out_handlers { + for handler in &mut self.out_handlers { handler.inject_event(NotifsOutHandlerIn::Disable); } } self.enabled = EnabledState::Disabled; for num in self.pending_in.drain(..) { - self.in_handlers[num].0.inject_event(NotifsInHandlerIn::Refuse); + self.in_handlers[num].inject_event(NotifsInHandlerIn::Refuse); } }, NotifsHandlerIn::SendLegacy { message } => self.legacy.inject_event(LegacyProtoHandlerIn::SendCustomMessage { message }), - NotifsHandlerIn::SendNotification { message, engine_id, protocol_name } => { - for (handler, ngn_id) in &mut self.out_handlers { + NotifsHandlerIn::SendNotification { message, encoded_fallback_message, protocol_name } => { + for handler in &mut self.out_handlers { if handler.protocol_name() != &protocol_name[..] { - break; + continue; } if handler.is_open() { handler.inject_event(NotifsOutHandlerIn::Send(message)); return; - } else { - debug_assert_eq!(engine_id, *ngn_id); } } - let message = GenericMessage::<(), (), (), ()>::Consensus(ConsensusMessage { - engine_id, - data: message, - }); - self.legacy.inject_event(LegacyProtoHandlerIn::SendCustomMessage { - message: message.encode() + message: encoded_fallback_message, }); }, } @@ -362,21 +348,21 @@ impl ProtocolsHandler for NotifsHandler { ) { match (err, num) { (ProtocolsHandlerUpgrErr::Timeout, Some(num)) => - self.out_handlers[num].0.inject_dial_upgrade_error( + self.out_handlers[num].inject_dial_upgrade_error( (), ProtocolsHandlerUpgrErr::Timeout ), (ProtocolsHandlerUpgrErr::Timeout, None) => self.legacy.inject_dial_upgrade_error((), ProtocolsHandlerUpgrErr::Timeout), (ProtocolsHandlerUpgrErr::Timer, Some(num)) => - self.out_handlers[num].0.inject_dial_upgrade_error( + self.out_handlers[num].inject_dial_upgrade_error( (), ProtocolsHandlerUpgrErr::Timer ), (ProtocolsHandlerUpgrErr::Timer, None) => self.legacy.inject_dial_upgrade_error((), ProtocolsHandlerUpgrErr::Timer), (ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Select(err)), Some(num)) => - self.out_handlers[num].0.inject_dial_upgrade_error( + self.out_handlers[num].inject_dial_upgrade_error( (), ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Select(err)) ), @@ -386,7 +372,7 @@ impl ProtocolsHandler for NotifsHandler { ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Select(err)) ), (ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Apply(EitherError::A(err))), Some(num)) => - self.out_handlers[num].0.inject_dial_upgrade_error( + self.out_handlers[num].inject_dial_upgrade_error( (), ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Apply(err)) ), @@ -407,7 +393,7 @@ impl ProtocolsHandler for NotifsHandler { return KeepAlive::Yes; } - for (handler, _) in &self.in_handlers { + for handler in &self.in_handlers { let val = handler.connection_keep_alive(); if val.is_yes() { return KeepAlive::Yes; @@ -415,7 +401,7 @@ impl ProtocolsHandler for NotifsHandler { if ret < val { ret = val; } } - for (handler, _) in &self.out_handlers { + for handler in &self.out_handlers { let val = handler.connection_keep_alive(); if val.is_yes() { return KeepAlive::Yes; @@ -432,7 +418,7 @@ impl ProtocolsHandler for NotifsHandler { ) -> Poll< ProtocolsHandlerEvent > { - for (handler_num, (handler, engine_id)) in self.in_handlers.iter_mut().enumerate() { + for (handler_num, handler) in self.in_handlers.iter_mut().enumerate() { while let Poll::Ready(ev) = handler.poll(cx) { match ev { ProtocolsHandlerEvent::OutboundSubstreamRequest { .. } => @@ -453,7 +439,6 @@ impl ProtocolsHandler for NotifsHandler { if self.legacy.is_open() { let msg = NotifsHandlerOut::Notification { message, - engine_id: *engine_id, protocol_name: handler.protocol_name().to_owned().into(), }; return Poll::Ready(ProtocolsHandlerEvent::Custom(msg)); @@ -463,7 +448,7 @@ impl ProtocolsHandler for NotifsHandler { } } - for (handler_num, (handler, _)) in self.out_handlers.iter_mut().enumerate() { + for (handler_num, handler) in self.out_handlers.iter_mut().enumerate() { while let Poll::Ready(ev) = handler.poll(cx) { match ev { ProtocolsHandlerEvent::OutboundSubstreamRequest { protocol, info: () } => diff --git a/client/network/src/protocol/generic_proto/handler/notif_in.rs b/client/network/src/protocol/generic_proto/handler/notif_in.rs index 4e16fb1af419f4aebb2748b09c477119f1165309..7558d1d361fd7cf445271b4351cd0c7c5fdd45d7 100644 --- a/client/network/src/protocol/generic_proto/handler/notif_in.rs +++ b/client/network/src/protocol/generic_proto/handler/notif_in.rs @@ -36,7 +36,7 @@ use libp2p::swarm::{ }; use log::{error, warn}; use smallvec::SmallVec; -use std::{borrow::Cow, fmt, pin::Pin, str, task::{Context, Poll}}; +use std::{borrow::Cow, fmt, pin::Pin, task::{Context, Poll}}; /// Implements the `IntoProtocolsHandler` trait of libp2p. /// @@ -156,16 +156,19 @@ impl ProtocolsHandler for NotifsInHandler { &mut self, (msg, proto): >::Output ) { + // If a substream already exists, we drop it and replace it with the new incoming one. if self.substream.is_some() { - warn!( - target: "sub-libp2p", - "Received duplicate inbound notifications substream for {:?}", - str::from_utf8(self.in_protocol.protocol_name()), - ); - return; + self.events_queue.push(ProtocolsHandlerEvent::Custom(NotifsInHandlerOut::Closed)); } + // Note that we drop the existing substream, which will send an equivalent to a TCP "RST" + // to the remote and force-close the substream. It might seem like an unclean way to get + // rid of a substream. However, keep in mind that it is invalid for the remote to open + // multiple such substreams, and therefore sending a "RST" is the correct thing to do. + // Also note that we have already closed our writing side during the initial handshake, + // and we can't close "more" than that anyway. self.substream = Some(proto); + self.events_queue.push(ProtocolsHandlerEvent::Custom(NotifsInHandlerOut::OpenRequest(msg))); self.pending_accept_refuses = self.pending_accept_refuses .checked_add(1) @@ -235,8 +238,15 @@ impl ProtocolsHandler for NotifsInHandler { match self.substream.as_mut().map(|s| Stream::poll_next(Pin::new(s), cx)) { None | Some(Poll::Pending) => {}, - Some(Poll::Ready(Some(Ok(msg)))) => - return Poll::Ready(ProtocolsHandlerEvent::Custom(NotifsInHandlerOut::Notif(msg))), + Some(Poll::Ready(Some(Ok(msg)))) => { + if self.pending_accept_refuses != 0 { + warn!( + target: "sub-libp2p", + "Bad state in inbound-only handler: notif before accepting substream" + ); + } + return Poll::Ready(ProtocolsHandlerEvent::Custom(NotifsInHandlerOut::Notif(msg))) + }, Some(Poll::Ready(None)) | Some(Poll::Ready(Some(Err(_)))) => { self.substream = None; return Poll::Ready(ProtocolsHandlerEvent::Custom(NotifsInHandlerOut::Closed)); diff --git a/client/network/src/protocol/generic_proto/tests.rs b/client/network/src/protocol/generic_proto/tests.rs index b8436e2c7f704bf2c4d5664c6908fce96c80d65c..00b840d5811121bbf39cfe7f37bd52aa56241e1d 100644 --- a/client/network/src/protocol/generic_proto/tests.rs +++ b/client/network/src/protocol/generic_proto/tests.rs @@ -26,7 +26,7 @@ use libp2p::{PeerId, Multiaddr, Transport}; use rand::seq::SliceRandom; use std::{error, io, task::Context, task::Poll, time::Duration}; use std::collections::HashSet; -use crate::message::{generic::BlockResponse, Message}; +use crate::protocol::message::{generic::BlockResponse, Message}; use crate::protocol::generic_proto::{GenericProto, GenericProtoOut}; use sp_test_primitives::Block; @@ -245,7 +245,7 @@ fn two_nodes_transfer_lots_of_packets() { loop { match ready!(service2.poll_next_unpin(cx)) { Some(GenericProtoOut::CustomProtocolOpen { .. }) => {}, - Some(GenericProtoOut::CustomMessage { message, .. }) => { + Some(GenericProtoOut::LegacyMessage { message, .. }) => { match Message::::decode(&mut &message[..]).unwrap() { Message::::BlockResponse(BlockResponse { id: _, blocks }) => { assert!(blocks.is_empty()); @@ -315,7 +315,7 @@ fn basic_two_nodes_requests_in_parallel() { loop { match ready!(service2.poll_next_unpin(cx)) { Some(GenericProtoOut::CustomProtocolOpen { .. }) => {}, - Some(GenericProtoOut::CustomMessage { message, .. }) => { + Some(GenericProtoOut::LegacyMessage { message, .. }) => { let pos = to_receive.iter().position(|m| m.encode() == message).unwrap(); to_receive.remove(pos); if to_receive.is_empty() { @@ -345,13 +345,21 @@ fn reconnect_after_disconnect() { let mut service1_state = ServiceState::NotConnected; let mut service2_state = ServiceState::NotConnected; - // Run the events loops. - futures::executor::block_on(future::poll_fn(|cx| -> Poll> { + futures::executor::block_on(async move { loop { - let mut service1_not_ready = false; + // Grab next event from services. + let event = { + let s1 = service1.next(); + let s2 = service2.next(); + futures::pin_mut!(s1, s2); + match future::select(s1, s2).await { + future::Either::Left((ev, _)) => future::Either::Left(ev), + future::Either::Right((ev, _)) => future::Either::Right(ev), + } + }; - match service1.poll_next_unpin(cx) { - Poll::Ready(Some(GenericProtoOut::CustomProtocolOpen { .. })) => { + match event { + future::Either::Left(GenericProtoOut::CustomProtocolOpen { .. }) => { match service1_state { ServiceState::NotConnected => { service1_state = ServiceState::FirstConnec; @@ -363,19 +371,14 @@ fn reconnect_after_disconnect() { ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(), } }, - Poll::Ready(Some(GenericProtoOut::CustomProtocolClosed { .. })) => { + future::Either::Left(GenericProtoOut::CustomProtocolClosed { .. }) => { match service1_state { ServiceState::FirstConnec => service1_state = ServiceState::Disconnected, ServiceState::ConnectedAgain| ServiceState::NotConnected | ServiceState::Disconnected => panic!(), } }, - Poll::Pending => service1_not_ready = true, - _ => panic!() - } - - match service2.poll_next_unpin(cx) { - Poll::Ready(Some(GenericProtoOut::CustomProtocolOpen { .. })) => { + future::Either::Right(GenericProtoOut::CustomProtocolOpen { .. }) => { match service2_state { ServiceState::NotConnected => { service2_state = ServiceState::FirstConnec; @@ -387,43 +390,43 @@ fn reconnect_after_disconnect() { ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(), } }, - Poll::Ready(Some(GenericProtoOut::CustomProtocolClosed { .. })) => { + future::Either::Right(GenericProtoOut::CustomProtocolClosed { .. }) => { match service2_state { ServiceState::FirstConnec => service2_state = ServiceState::Disconnected, ServiceState::ConnectedAgain| ServiceState::NotConnected | ServiceState::Disconnected => panic!(), } }, - Poll::Pending if service1_not_ready => break, - Poll::Pending => {} - _ => panic!() + _ => {} } - } - if service1_state == ServiceState::ConnectedAgain && service2_state == ServiceState::ConnectedAgain { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - })).unwrap(); - - // Do a second 3-seconds run to make sure we don't get disconnected immediately again. - let mut delay = futures_timer::Delay::new(Duration::from_secs(3)); - futures::executor::block_on(future::poll_fn(|cx| -> Poll> { - match service1.poll_next_unpin(cx) { - Poll::Pending => {}, - _ => panic!() + if service1_state == ServiceState::ConnectedAgain && service2_state == ServiceState::ConnectedAgain { + break; + } } - match service2.poll_next_unpin(cx) { - Poll::Pending => {}, - _ => panic!() - } + // Now that the two services have disconnected and reconnected, wait for 3 seconds and + // check whether they're still connected. + let mut delay = futures_timer::Delay::new(Duration::from_secs(3)); - if let Poll::Ready(()) = delay.poll_unpin(cx) { - Poll::Ready(Ok(())) - } else { - Poll::Pending + loop { + // Grab next event from services. + let event = { + let s1 = service1.next(); + let s2 = service2.next(); + futures::pin_mut!(s1, s2); + match future::select(future::select(s1, s2), &mut delay).await { + future::Either::Right(_) => break, // success + future::Either::Left((future::Either::Left((ev, _)), _)) => ev, + future::Either::Left((future::Either::Right((ev, _)), _)) => ev, + } + }; + + match event { + GenericProtoOut::CustomProtocolOpen { .. } | + GenericProtoOut::CustomProtocolClosed { .. } => panic!(), + _ => {} + } } - })).unwrap(); + }); } diff --git a/client/network/src/protocol/generic_proto/upgrade/notifications.rs b/client/network/src/protocol/generic_proto/upgrade/notifications.rs index ddc07b5d6f3d6b2ffefe8fec47cee78768a1495a..b6ae1425f1161b73498c5556adb368a3332f4f35 100644 --- a/client/network/src/protocol/generic_proto/upgrade/notifications.rs +++ b/client/network/src/protocol/generic_proto/upgrade/notifications.rs @@ -164,12 +164,9 @@ where TSubstream: AsyncRead + AsyncWrite, { /// Sends the handshake in order to inform the remote that we accept the substream. pub fn send_handshake(&mut self, message: impl Into>) { - match self.handshake { - NotificationsInSubstreamHandshake::NotSent => {} - _ => { - error!(target: "sub-libp2p", "Tried to send handshake twice"); - return; - } + if !matches!(self.handshake, NotificationsInSubstreamHandshake::NotSent) { + error!(target: "sub-libp2p", "Tried to send handshake twice"); + return; } self.handshake = NotificationsInSubstreamHandshake::PendingSend(message.into()); @@ -189,8 +186,10 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin, match mem::replace(this.handshake, NotificationsInSubstreamHandshake::Sent) { NotificationsInSubstreamHandshake::Sent => return Stream::poll_next(this.socket.as_mut(), cx), - NotificationsInSubstreamHandshake::NotSent => - return Poll::Pending, + NotificationsInSubstreamHandshake::NotSent => { + *this.handshake = NotificationsInSubstreamHandshake::NotSent; + return Poll::Pending + }, NotificationsInSubstreamHandshake::PendingSend(msg) => match Sink::poll_ready(this.socket.as_mut(), cx) { Poll::Ready(_) => { @@ -200,15 +199,19 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin, Err(err) => return Poll::Ready(Some(Err(err))), } }, - Poll::Pending => - *this.handshake = NotificationsInSubstreamHandshake::PendingSend(msg), + Poll::Pending => { + *this.handshake = NotificationsInSubstreamHandshake::PendingSend(msg); + return Poll::Pending + } }, NotificationsInSubstreamHandshake::Close => match Sink::poll_close(this.socket.as_mut(), cx)? { Poll::Ready(()) => *this.handshake = NotificationsInSubstreamHandshake::Sent, - Poll::Pending => - *this.handshake = NotificationsInSubstreamHandshake::Close, + Poll::Pending => { + *this.handshake = NotificationsInSubstreamHandshake::Close; + return Poll::Pending + } }, } } diff --git a/client/network/src/protocol/light_client_handler.rs b/client/network/src/protocol/light_client_handler.rs index d48bd4b91b1b4721c52b8b18c2cb75e740e3908e..4c228205d30ded65ae4a37e01bfb53468eec35c0 100644 --- a/client/network/src/protocol/light_client_handler.rs +++ b/client/network/src/protocol/light_client_handler.rs @@ -51,11 +51,13 @@ use libp2p::{ }; use nohash_hasher::IntMap; use prost::Message; -use rustc_hex::ToHex; use sc_client::light::fetcher; use sc_client_api::StorageProof; use sc_peerset::ReputationChange; -use sp_core::storage::{ChildInfo, StorageKey}; +use sp_core::{ + storage::{ChildInfo, StorageKey}, + hexdisplay::HexDisplay, +}; use sp_blockchain::{Error as ClientError}; use sp_runtime::{ traits::{Block, Header, NumberFor, Zero}, @@ -75,7 +77,8 @@ use wasm_timer::Instant; /// Configuration options for `LightClientHandler` behaviour. #[derive(Debug, Clone)] pub struct Config { - max_data_size: usize, + max_request_size: usize, + max_response_size: usize, max_pending_requests: usize, inactivity_timeout: Duration, request_timeout: Duration, @@ -85,13 +88,15 @@ pub struct Config { impl Config { /// Create a fresh configuration with the following options: /// - /// - max. data size = 1 MiB + /// - max. request size = 1 MiB + /// - max. response size = 16 MiB /// - max. pending requests = 128 /// - inactivity timeout = 15s /// - request timeout = 15s pub fn new(id: &ProtocolId) -> Self { let mut c = Config { - max_data_size: 1024 * 1024, + max_request_size: 1 * 1024 * 1024, + max_response_size: 16 * 1024 * 1024, max_pending_requests: 128, inactivity_timeout: Duration::from_secs(15), request_timeout: Duration::from_secs(15), @@ -101,9 +106,15 @@ impl Config { c } - /// Limit the max. length of incoming request bytes. - pub fn set_max_data_size(&mut self, v: usize) -> &mut Self { - self.max_data_size = v; + /// Limit the max. length in bytes of a request. + pub fn set_max_request_size(&mut self, v: usize) -> &mut Self { + self.max_request_size = v; + self + } + + /// Limit the max. length in bytes of a response. + pub fn set_max_response_size(&mut self, v: usize) -> &mut Self { + self.max_response_size = v; self } @@ -504,7 +515,7 @@ where log::trace!("remote read child request from {} ({} {} at {:?})", peer, - request.storage_key.to_hex::(), + HexDisplay::from(&request.storage_key), fmt_keys(request.keys.first(), request.keys.last()), request.block); @@ -522,7 +533,7 @@ where Err(error) => { log::trace!("remote read child request from {} ({} {} at {:?}) failed with: {}", peer, - request.storage_key.to_hex::(), + HexDisplay::from(&request.storage_key), fmt_keys(request.keys.first(), request.keys.last()), request.block, error); @@ -532,7 +543,7 @@ where } else { log::trace!("remote read child request from {} ({} {} at {:?}) failed with: {}", peer, - request.storage_key.to_hex::(), + HexDisplay::from(&request.storage_key), fmt_keys(request.keys.first(), request.keys.last()), request.block, "invalid child info and type" @@ -585,9 +596,9 @@ where log::trace!("remote changes proof request from {} for key {} ({:?}..{:?})", peer, if !request.storage_key.is_empty() { - format!("{} : {}", request.storage_key.to_hex::(), request.key.to_hex::()) + format!("{} : {}", HexDisplay::from(&request.storage_key), HexDisplay::from(&request.key)) } else { - request.key.to_hex::() + HexDisplay::from(&request.key).to_string() }, request.first, request.last); @@ -610,9 +621,9 @@ where log::trace!("remote changes proof request from {} for key {} ({:?}..{:?}) failed with: {}", peer, if let Some(sk) = storage_key { - format!("{} : {}", sk.0.to_hex::(), key.0.to_hex::()) + format!("{} : {}", HexDisplay::from(&sk.0), HexDisplay::from(&key.0)) } else { - key.0.to_hex::() + HexDisplay::from(&key.0).to_string() }, request.first, request.last, @@ -652,7 +663,7 @@ where fn new_handler(&mut self) -> Self::ProtocolsHandler { let p = InboundProtocol { - max_data_size: self.config.max_data_size, + max_request_size: self.config.max_request_size, protocol: self.config.protocol.clone(), }; OneShotHandler::new(SubstreamProtocol::new(p), self.config.inactivity_timeout) @@ -839,7 +850,7 @@ where let protocol = OutboundProtocol { request: buf, request_id: id, - max_data_size: self.config.max_data_size, + max_response_size: self.config.max_response_size, protocol: self.config.protocol.clone(), }; self.peers.get_mut(&peer).map(|info| info.status = PeerStatus::BusyWith(id)); @@ -1006,7 +1017,7 @@ pub enum Event { #[derive(Debug, Clone)] pub struct InboundProtocol { /// The max. request length in bytes. - max_data_size: usize, + max_request_size: usize, /// The protocol to use for upgrade negotiation. protocol: Bytes, } @@ -1030,7 +1041,7 @@ where fn upgrade_inbound(self, mut s: T, _: Self::Info) -> Self::Future { let future = async move { - let vec = read_one(&mut s, self.max_data_size).await?; + let vec = read_one(&mut s, self.max_request_size).await?; match api::v1::light::Request::decode(&vec[..]) { Ok(r) => Ok(Event::Request(r, s)), Err(e) => Err(ReadOneError::Io(io::Error::new(io::ErrorKind::Other, e))) @@ -1049,8 +1060,8 @@ pub struct OutboundProtocol { request: Vec, /// Local identifier for the request. Used to associate it with a response. request_id: u64, - /// The max. request length in bytes. - max_data_size: usize, + /// The max. response length in bytes. + max_response_size: usize, /// The protocol to use for upgrade negotiation. protocol: Bytes, } @@ -1075,7 +1086,7 @@ where fn upgrade_outbound(self, mut s: T, _: Self::Info) -> Self::Future { let future = async move { write_one(&mut s, &self.request).await?; - let vec = read_one(&mut s, self.max_data_size).await?; + let vec = read_one(&mut s, self.max_response_size).await?; api::v1::light::Response::decode(&vec[..]) .map(|r| Event::Response(self.request_id, r)) .map_err(|e| { @@ -1089,9 +1100,9 @@ where fn fmt_keys(first: Option<&Vec>, last: Option<&Vec>) -> String { if let (Some(first), Some(last)) = (first, last) { if first == last { - first.to_hex::() + HexDisplay::from(first).to_string() } else { - format!("{}..{}", first.to_hex::(), last.to_hex::()) + format!("{}..{}", HexDisplay::from(first), HexDisplay::from(last)) } } else { String::from("n/a") diff --git a/client/network/src/protocol/light_dispatch.rs b/client/network/src/protocol/light_dispatch.rs index 8146172e1590868a2dcbd11de747701d7abbe8d7..39e90881fb0c64e081267cbae7eff8f60b205a46 100644 --- a/client/network/src/protocol/light_dispatch.rs +++ b/client/network/src/protocol/light_dispatch.rs @@ -270,7 +270,7 @@ impl LightDispatch where let request = match self.remove(peer.clone(), request_id) { Some(request) => request, None => { - info!("Invalid remote {} response from peer {}", rtype, peer); + info!("💔 Invalid remote {} response from peer {}", rtype, peer); network.report_peer(&peer, ReputationChange::new_fatal("Invalid remote response")); network.disconnect_peer(&peer); self.remove_peer(&peer); @@ -282,7 +282,7 @@ impl LightDispatch where let (retry_count, retry_request_data) = match try_accept(request, &self.checker) { Accept::Ok => (retry_count, None), Accept::CheckFailed(error, retry_request_data) => { - info!("Failed to check remote {} response from peer {}: {}", rtype, peer, error); + info!("💔 Failed to check remote {} response from peer {}: {}", rtype, peer, error); network.report_peer(&peer, ReputationChange::new_fatal("Failed remote response check")); network.disconnect_peer(&peer); self.remove_peer(&peer); @@ -296,7 +296,7 @@ impl LightDispatch where } }, Accept::Unexpected(retry_request_data) => { - info!("Unexpected response to remote {} from peer", rtype); + info!("💔 Unexpected response to remote {} from peer", rtype); network.report_peer(&peer, ReputationChange::new_fatal("Unexpected remote response")); network.disconnect_peer(&peer); self.remove_peer(&peer); @@ -689,7 +689,7 @@ pub mod tests { ChangesProof, RemoteCallRequest, RemoteReadRequest, RemoteReadChildRequest, RemoteChangesRequest, RemoteBodyRequest}; use crate::config::Roles; - use crate::message::{self, BlockAttributes, Direction, FromBlock, RequestId}; + use crate::protocol::message::{self, BlockAttributes, Direction, FromBlock, RequestId}; use libp2p::PeerId; use super::{REQUEST_TIMEOUT, LightDispatch, LightDispatchNetwork, RequestData, StorageProof}; use sp_test_primitives::{Block, Header}; diff --git a/client/network/src/protocol/sync.rs b/client/network/src/protocol/sync.rs index ffbe6a096ae69e9616e357d6342f0544790478f0..df12522d37ba91a85a4f76f15616315af6af83f0 100644 --- a/client/network/src/protocol/sync.rs +++ b/client/network/src/protocol/sync.rs @@ -85,7 +85,7 @@ mod rep { pub const INCOMPLETE_HEADER: Rep = Rep::new(-(1 << 20), "Incomplete header"); /// Reputation change for peers which send us a block which we fail to verify. - pub const VERIFICATION_FAIL: Rep = Rep::new(-(1 << 20), "Block verification failed"); + pub const VERIFICATION_FAIL: Rep = Rep::new(-(1 << 29), "Block verification failed"); /// Reputation change for peers which send us a known bad block. pub const BAD_BLOCK: Rep = Rep::new(-(1 << 29), "Bad block"); @@ -138,6 +138,8 @@ pub struct ChainSync { block_announce_validator: Box + Send>, /// Maximum number of peers to ask the same blocks in parallel. max_parallel_downloads: u32, + /// Total number of processed blocks (imported or failed). + processed_blocks: usize, } /// All the data we have about a Peer that we are trying to sync with @@ -318,6 +320,7 @@ impl ChainSync { is_idle: false, block_announce_validator, max_parallel_downloads, + processed_blocks: 0, } } @@ -357,6 +360,11 @@ impl ChainSync { self.fork_targets.len() } + /// Number of processed blocks. + pub fn num_processed_blocks(&self) -> usize { + self.processed_blocks + } + /// Handle a new connected peer. /// /// Call this method whenever we connect to a new peer. @@ -370,12 +378,12 @@ impl ChainSync { Err(BadPeer(who, rep::BLOCKCHAIN_READ_ERROR)) } Ok(BlockStatus::KnownBad) => { - info!("New peer with known bad best block {} ({}).", best_hash, best_number); + info!("💔 New peer with known bad best block {} ({}).", best_hash, best_number); Err(BadPeer(who, rep::BAD_BLOCK)) } Ok(BlockStatus::Unknown) => { if best_number.is_zero() { - info!("New peer with unknown genesis hash {} ({}).", best_hash, best_number); + info!("💔 New peer with unknown genesis hash {} ({}).", best_hash, best_number); return Err(BadPeer(who, rep::GENESIS_MISMATCH)); } // If there are more than `MAJOR_SYNC_BLOCKS` in the import queue then we have @@ -649,7 +657,7 @@ impl ChainSync { pub fn on_block_data (&mut self, who: PeerId, request: Option>, response: BlockResponse) -> Result, BadPeer> { - let new_blocks: Vec> = + let mut 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) { @@ -703,7 +711,7 @@ impl ChainSync { return Err(BadPeer(who, rep::UNKNOWN_ANCESTOR)) }, (_, Err(e)) => { - info!("Error answering legitimate blockchain query: {:?}", e); + info!("❌ Error answering legitimate blockchain query: {:?}", e); return Err(BadPeer(who, rep::BLOCKCHAIN_READ_ERROR)) } }; @@ -768,6 +776,12 @@ impl ChainSync { Vec::new() }; + 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 is_recent = new_blocks.first() .map(|block| { self.peers.iter().any(|(_, peer)| peer.recently_announced.contains(&block.hash)) @@ -895,10 +909,12 @@ impl ChainSync { let mut output = Vec::new(); let mut has_error = false; - let mut hashes = vec![]; - for (result, hash) in results { - hashes.push(hash); + for (_, hash) in &results { + self.queue_blocks.remove(&hash); + } + self.processed_blocks += results.len(); + for (result, hash) in results { if has_error { continue; } @@ -927,7 +943,7 @@ impl ChainSync { if aux.bad_justification { if let Some(peer) = who { - info!("Sent block with bad justification to import"); + info!("💔 Sent block with bad justification to import"); output.push(Err(BadPeer(peer, rep::BAD_JUSTIFICATION))); } } @@ -943,43 +959,39 @@ impl ChainSync { }, Err(BlockImportError::IncompleteHeader(who)) => { if let Some(peer) = who { - info!("Peer sent block with incomplete header to import"); + warn!("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 { - info!("Verification failed from peer: {}", e); + warn!("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 { - info!("Block received from peer has been blacklisted"); + info!("💔 Block {:?} received from peer {} has been blacklisted", hash, peer); output.push(Err(BadPeer(peer, rep::BAD_BLOCK))); - output.extend(self.restart()); } }, 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"); + trace!(target: "sync", "Obsolete block {:?}", hash); }, - Err(BlockImportError::UnknownParent) | - Err(BlockImportError::Cancelled) | - Err(BlockImportError::Other(_)) => { + e @ Err(BlockImportError::UnknownParent) | + e @ Err(BlockImportError::Other(_)) => { + warn!(target: "sync", "Error importing block {:?}: {:?}", hash, e); output.extend(self.restart()); }, + Err(BlockImportError::Cancelled) => {} }; } - for hash in hashes { - self.queue_blocks.remove(&hash); - } - self.is_idle = false; output.into_iter() } @@ -1094,12 +1106,14 @@ impl ChainSync { if let PeerSyncState::AncestorSearch(_, _) = peer.state { return OnBlockAnnounce::Nothing } - // If the announced block is the best they have seen, our common number + // 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 { + if known && self.best_queued_number >= number { peer.common_number = number - } else if header.parent_hash() == &self.best_queued_hash || known_parent { + } else if header.parent_hash() == &self.best_queued_hash + || known_parent && self.best_queued_number >= number + { peer.common_number = number - One::one(); } } @@ -1168,7 +1182,7 @@ impl ChainSync { /// Restart the sync process. fn restart<'a>(&'a mut self) -> impl Iterator), BadPeer>> + 'a { - self.queue_blocks.clear(); + self.processed_blocks = 0; self.blocks.clear(); let info = self.client.info(); self.best_queued_hash = info.best_hash; @@ -1308,13 +1322,17 @@ fn peer_block_request( finalized: NumberFor, best_num: NumberFor, ) -> Option<(Range>, BlockRequest)> { - if peer.common_number < finalized { - return None; - } if best_num >= peer.best_number { // Will be downloaded as alternative fork instead. return None; } + if peer.common_number < finalized { + trace!( + target: "sync", + "Requesting pre-finalized chain from {:?}, common={}, finalized={}, peer best={}, our best={}", + id, finalized, peer.common_number, peer.best_number, best_num, + ); + } if let Some(range) = blocks.needed_blocks( id.clone(), MAX_BLOCKS_TO_REQUEST, diff --git a/client/network/src/protocol/sync/blocks.rs b/client/network/src/protocol/sync/blocks.rs index 31b798ace28a54b207df5d43e625fb2d0c9170f2..359287701e66e5b95b9561bbd5982fd50e77e08d 100644 --- a/client/network/src/protocol/sync/blocks.rs +++ b/client/network/src/protocol/sync/blocks.rs @@ -212,7 +212,7 @@ impl BlockCollection { #[cfg(test)] mod test { use super::{BlockCollection, BlockData, BlockRangeState}; - use crate::{message, PeerId}; + use crate::{protocol::message, PeerId}; use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper}; use sp_core::H256; diff --git a/client/network/src/protocol/sync/extra_requests.rs b/client/network/src/protocol/sync/extra_requests.rs index 81b12a1a704aab9b17737c4a03190bef519ff3c5..3d854b574b01fc26aaf6d1d39e15bed2b46551c2 100644 --- a/client/network/src/protocol/sync/extra_requests.rs +++ b/client/network/src/protocol/sync/extra_requests.rs @@ -221,7 +221,7 @@ impl ExtraRequests { }; if self.tree.finalize_root(&finalized_hash).is_none() { - warn!(target: "sync", "Imported {:?} {:?} which isn't a root in the tree: {:?}", + warn!(target: "sync", "‼️ Imported {:?} {:?} which isn't a root in the tree: {:?}", finalized_hash, finalized_number, self.tree.roots().collect::>() diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 811690f433fc3739b29a659f11af5efe12a27b5b..2c93d70e268bc9a2c01935f0df7ac28c35bf9475 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -171,18 +171,22 @@ impl NetworkWorker { let mut known_addresses = Vec::new(); let mut bootnodes = Vec::new(); let mut reserved_nodes = Vec::new(); + let mut boot_node_ids = HashSet::new(); // Process the bootnodes. for bootnode in params.network_config.boot_nodes.iter() { match parse_str_addr(bootnode) { Ok((peer_id, addr)) => { bootnodes.push(peer_id.clone()); + boot_node_ids.insert(peer_id.clone()); known_addresses.push((peer_id, addr)); }, Err(_) => warn!(target: "sub-libp2p", "Not a valid bootnode address: {}", bootnode), } } + let boot_node_ids = Arc::new(boot_node_ids); + // Check for duplicate bootnodes. known_addresses.iter() .try_for_each(|(peer_id, addr)| @@ -243,7 +247,8 @@ impl NetworkWorker { params.protocol_id.clone(), peerset_config, params.block_announce_validator, - params.metrics_registry.as_ref() + params.metrics_registry.as_ref(), + boot_node_ids.clone(), )?; // Build the swarm. @@ -331,7 +336,8 @@ impl NetworkWorker { metrics: match params.metrics_registry { Some(registry) => Some(Metrics::register(®istry)?), None => None - } + }, + boot_node_ids, }) } @@ -375,6 +381,11 @@ impl NetworkWorker { self.network_service.user_protocol().num_queued_blocks() } + /// Returns the number of processed blocks. + pub fn num_processed_blocks(&self) -> usize { + self.network_service.user_protocol().num_processed_blocks() + } + /// Number of active sync requests. pub fn num_sync_requests(&self) -> usize { self.network_service.user_protocol().num_sync_requests() @@ -392,8 +403,8 @@ impl NetworkWorker { } /// You must call this when a new block is imported by the client. - pub fn on_block_imported(&mut self, header: B::Header, data: Vec, is_best: bool) { - self.network_service.user_protocol_mut().on_block_imported(&header, data, is_best); + pub fn on_block_imported(&mut self, header: B::Header, is_best: bool) { + self.network_service.user_protocol_mut().on_block_imported(&header, is_best); } /// You must call this when a new block is finalized by the client. @@ -767,6 +778,8 @@ pub struct NetworkWorker { event_streams: Vec>, /// Prometheus network metrics. metrics: Option, + /// The `PeerId`'s of all boot nodes. + boot_node_ids: Arc>, } struct Metrics { @@ -986,8 +999,29 @@ impl Future for NetworkWorker { trace!(target: "sub-libp2p", "Libp2p => NewListenAddr({})", addr), Poll::Ready(SwarmEvent::ExpiredListenAddr(addr)) => trace!(target: "sub-libp2p", "Libp2p => ExpiredListenAddr({})", addr), - Poll::Ready(SwarmEvent::UnreachableAddr { peer_id, address, error }) => - trace!(target: "sub-libp2p", "Libp2p => Failed to reach {:?} through {:?}: {}", peer_id, address, error), + Poll::Ready(SwarmEvent::UnreachableAddr { peer_id, address, error }) => { + let error = error.to_string(); + + trace!( + target: "sub-libp2p", "Libp2p => Failed to reach {:?} through {:?}: {}", + peer_id, + address, + error, + ); + + if let Some(peer_id) = peer_id { + if this.boot_node_ids.contains(&peer_id) + && error.contains("Peer ID mismatch") + { + error!( + "Connecting to bootnode with peer id `{}` and address `{}` failed \ + because it returned a different peer id!", + peer_id, + address, + ); + } + } + }, Poll::Ready(SwarmEvent::StartConnect(peer_id)) => trace!(target: "sub-libp2p", "Libp2p => StartConnect({:?})", peer_id), }; @@ -1056,7 +1090,7 @@ impl<'a, B: BlockT, H: ExHashT> Link for NetworkLink<'a, B, H> { fn justification_imported(&mut self, who: PeerId, hash: &B::Hash, number: NumberFor, success: bool) { self.protocol.user_protocol_mut().justification_import_result(hash.clone(), number, success); if !success { - info!("Invalid justification provided by {} for #{}", who, hash); + info!("💔 Invalid justification provided by {} for #{}", who, hash); self.protocol.user_protocol_mut().disconnect_peer(&who); self.protocol.user_protocol_mut().report_peer(who, ReputationChange::new_fatal("Invalid justification")); } @@ -1076,7 +1110,7 @@ impl<'a, B: BlockT, H: ExHashT> Link for NetworkLink<'a, B, H> { let success = finalization_result.is_ok(); self.protocol.user_protocol_mut().finality_proof_import_result(request_block, finalization_result); if !success { - info!("Invalid finality proof provided by {} for #{}", who, request_block.0); + info!("💔 Invalid finality proof provided by {} for #{}", who, request_block.0); self.protocol.user_protocol_mut().disconnect_peer(&who); self.protocol.user_protocol_mut().report_peer(who, ReputationChange::new_fatal("Invalid finality proof")); } diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 7fec4f4da8b47ccca2df942dadf729e8357583dd..769e0faca9e5cd8a1055515f5c9abd66df22b915 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -10,22 +10,25 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sc-network = { version = "0.8.0-alpha.2", path = "../" } +sc-network = { version = "0.8.0-alpha.5", path = "../" } log = "0.4.8" parking_lot = "0.10.0" -futures = "0.3.1" +futures = "0.3.4" futures-timer = "3.0.1" rand = "0.7.2" libp2p = { version = "0.16.2", default-features = false, features = ["libp2p-websocket"] } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } -sc-client = { version = "0.8.0-alpha.2", path = "../../" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../../api" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sc-block-builder = { version = "0.8.0-alpha.2", path = "../../block-builder" } -sp-consensus-babe = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/babe" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } +sc-client = { version = "0.8.0-alpha.5", path = "../../" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../api" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "../../block-builder" } +sp-consensus-babe = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/babe" } env_logger = "0.7.0" substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../../test-utils/runtime/client" } substrate-test-runtime = { version = "2.0.0-dev", path = "../../../test-utils/runtime" } tempfile = "3.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index e98cf8bada0bcfd8ae431bb62fa9e7f2e357b0c3..8a4909277c15264e243806bff1eb91e7f375e387 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -207,6 +207,11 @@ impl Peer { self.network.num_connected_peers() } + /// Returns the number of processed blocks. + pub fn num_processed_blocks(&self) -> usize { + self.network.num_processed_blocks() + } + /// Returns true if we have no peer. pub fn is_offline(&self) -> bool { self.num_peers() == 0 @@ -276,7 +281,8 @@ impl Peer { Default::default() }; self.block_import.import_block(import_block, cache).expect("block_import failed"); - self.network.on_block_imported(header, Vec::new(), true); + self.network.on_block_imported(header, true); + self.network.service().announce_block(hash, Vec::new()); at = hash; } @@ -780,9 +786,9 @@ pub trait TestNetFactory: Sized { while let Poll::Ready(Some(notification)) = peer.imported_blocks_stream.as_mut().poll_next(cx) { peer.network.on_block_imported( notification.header, - Vec::new(), true, ); + peer.network.service().announce_block(notification.hash, Vec::new()); } // We poll `finality_notification_stream`, but we only take the last event. diff --git a/client/network/test/src/sync.rs b/client/network/test/src/sync.rs index 388257516832002de97775bbeb21c6696eaae38c..785b71cb79a762435f01247b0a4a7e8ddf6bd817 100644 --- a/client/network/test/src/sync.rs +++ b/client/network/test/src/sync.rs @@ -657,3 +657,69 @@ fn full_sync_requires_block_body() { net.block_until_idle(); assert_eq!(net.peer(1).client.info().best_number, 0); } + +#[test] +fn imports_stale_once() { + let _ = ::env_logger::try_init(); + + fn import_with_announce(net: &mut TestNet, hash: H256) { + // Announce twice + net.peer(0).announce_block(hash, Vec::new()); + net.peer(0).announce_block(hash, Vec::new()); + + 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 2 full nodes + let mut net = TestNet::new(2); + + // 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); + assert_eq!(net.peer(1).num_processed_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); + assert_eq!(net.peer(1).num_processed_blocks(), 2); +} + +#[test] +fn can_sync_to_peers_with_wrong_common_block() { + let _ = ::env_logger::try_init(); + let mut net = TestNet::new(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 + 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 + } else { + Poll::Ready(()) + } + })); + + // both peers re-org to the same fork without notifying each other + net.peer(0).client().finalize_block(BlockId::Hash(fork_hash), Some(Vec::new()), true).unwrap(); + net.peer(1).client().finalize_block(BlockId::Hash(fork_hash), Some(Vec::new()), true).unwrap(); + let final_hash = net.peer(0).push_blocks(1, false); + + net.block_until_sync(); + + assert!(net.peer(1).client().header(&BlockId::Hash(final_hash)).unwrap().is_some()); +} + diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index 963663ff3ecc5de82401d202823ac490fdfa840b..594f21a636e4451cc0987bab00131f7067885ec4 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Substrate offchain workers" name = "sc-offchain" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" license = "GPL-3.0" authors = ["Parity Technologies "] edition = "2018" @@ -10,22 +10,22 @@ repository = "https://github.com/paritytech/substrate/" [dependencies] bytes = "0.5" -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api" } fnv = "1.0.6" -futures = "0.3.1" +futures = "0.3.4" futures-timer = "3.0.1" log = "0.4.8" threadpool = "1.7" num_cpus = "1.10" -sp-offchain = { version = "2.0.0-alpha.2", path = "../../primitives/offchain" } -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } +sp-offchain = { version = "2.0.0-alpha.5", path = "../../primitives/offchain" } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } parking_lot = "0.10.0" -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } rand = "0.7.2" -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sc-network = { version = "0.8.0-alpha.2", path = "../network" } -sc-keystore = { version = "2.0.0-alpha.2", path = "../keystore" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sc-network = { version = "0.8.0-alpha.5", path = "../network" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../keystore" } [target.'cfg(not(target_os = "unknown"))'.dependencies] hyper = "0.13.2" @@ -33,12 +33,15 @@ hyper-rustls = "0.20" [dev-dependencies] env_logger = "0.7.0" -fdlimit = "0.1" -sc-client-db = { version = "0.8.0-alpha.2", default-features = true, path = "../db/" } -sc-transaction-pool = { version = "2.0.0-alpha.2", path = "../../client/transaction-pool" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../primitives/transaction-pool" } +fdlimit = "0.1.4" +sc-client-db = { version = "0.8.0-alpha.5", default-features = true, path = "../db/" } +sc-transaction-pool = { version = "2.0.0-alpha.5", path = "../../client/transaction-pool" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../primitives/transaction-pool" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client" } tokio = "0.2" [features] default = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/offchain/src/lib.rs b/client/offchain/src/lib.rs index 27a7f508459ba1bbc3cc4ba0eef0ed2a7c5f9770..94850e3fd3461c3cf79ecd48e905051991324941 100644 --- a/client/offchain/src/lib.rs +++ b/client/offchain/src/lib.rs @@ -191,7 +191,8 @@ mod tests { at: &BlockId, extrinsic: ::Extrinsic, ) -> Result<(), ()> { - futures::executor::block_on(self.0.submit_one(&at, extrinsic)) + let source = sp_transaction_pool::TransactionSource::Local; + futures::executor::block_on(self.0.submit_one(&at, source, extrinsic)) .map(|_| ()) .map_err(|_| ()) } diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index 9e76b8015afcadf897faf017a5ba935270cd5eef..0ad96c0c6ec8c7b9a6f634a849be70b4b3fa093c 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -3,7 +3,7 @@ description = "Connectivity manager based on reputation" homepage = "http://parity.io" license = "GPL-3.0" name = "sc-peerset" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" repository = "https://github.com/paritytech/substrate/" @@ -11,7 +11,7 @@ documentation = "https://docs.rs/sc-peerset" [dependencies] -futures = "0.3.1" +futures = "0.3.4" libp2p = { version = "0.16.2", default-features = false } log = "0.4.8" serde_json = "1.0.41" @@ -19,3 +19,6 @@ wasm-timer = "0.2" [dev-dependencies] rand = "0.7.2" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 0f3b72e519ba5cbff72236a885deb54101347594..c5043b5b0782b72d00e0f9940d0f6e87f9963c9c 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-rpc-api" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,19 +9,22 @@ repository = "https://github.com/paritytech/substrate/" description = "Substrate RPC interfaces." [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0" } +codec = { package = "parity-scale-codec", version = "1.3.0" } derive_more = "0.99.2" futures = { version = "0.3.1", features = ["compat"] } jsonrpc-core = "14.0.3" -jsonrpc-core-client = "14.0.3" +jsonrpc-core-client = "14.0.5" jsonrpc-derive = "14.0.3" jsonrpc-pubsub = "14.0.3" log = "0.4.8" parking_lot = "0.10.0" -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-version = { version = "2.0.0-alpha.2", path = "../../primitives/version" } -sp-runtime = { path = "../../primitives/runtime" , version = "2.0.0-alpha.2"} +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-version = { version = "2.0.0-alpha.5", path = "../../primitives/version" } +sp-runtime = { path = "../../primitives/runtime" , version = "2.0.0-alpha.5"} serde = { version = "1.0.101", features = ["derive"] } serde_json = "1.0.41" -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../primitives/transaction-pool" } -sp-rpc = { version = "2.0.0-alpha.2", path = "../../primitives/rpc" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../primitives/transaction-pool" } +sp-rpc = { version = "2.0.0-alpha.5", path = "../../primitives/rpc" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/rpc-api/src/state/mod.rs b/client/rpc-api/src/state/mod.rs index b2cf8ce909b20fead05a3be7bdb9d0d93d6fade1..d29e46a4b5637cf85d0745d59a73b0ad69afde7c 100644 --- a/client/rpc-api/src/state/mod.rs +++ b/client/rpc-api/src/state/mod.rs @@ -136,6 +136,14 @@ pub trait StateApi { hash: Option ) -> FutureResult>>; + /// Query storage entries (by key) starting at block hash given as the second parameter. + #[rpc(name = "state_queryStorageAt")] + fn query_storage_at( + &self, + keys: Vec, + at: Option, + ) -> FutureResult>>; + /// New runtime version subscription #[pubsub( subscription = "state_runtimeVersion", diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index 79d2984c2293e0649456ce5453f0dd719cfe77ad..c834d7dbf7396b89a6a72ccaaf7feca3059e7507 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-rpc-server" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -14,8 +14,11 @@ pubsub = { package = "jsonrpc-pubsub", version = "14.0.3" } log = "0.4.8" serde = "1.0.101" serde_json = "1.0.41" -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } [target.'cfg(not(target_os = "unknown"))'.dependencies] http = { package = "jsonrpc-http-server", version = "14.0.3" } ws = { package = "jsonrpc-ws-server", version = "14.0.3" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index f99e25b14ee2a9bb7905a3ab42cf119af05a728e..7b06113dda90e89ca4611f7dbfb62e379e30b2f8 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-rpc" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,37 +9,39 @@ repository = "https://github.com/paritytech/substrate/" description = "Substrate Client RPC" [dependencies] -sc-rpc-api = { version = "0.8.0-alpha.2", path = "../rpc-api" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sc-client = { version = "0.8.0-alpha.2", path = "../" } -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api" } -codec = { package = "parity-scale-codec", version = "1.2.0" } +sc-rpc-api = { version = "0.8.0-alpha.5", path = "../rpc-api" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sc-client = { version = "0.8.0-alpha.5", path = "../" } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api" } +codec = { package = "parity-scale-codec", version = "1.3.0" } futures = { version = "0.3.1", features = ["compat"] } jsonrpc-pubsub = "14.0.3" log = "0.4.8" -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } rpc = { package = "jsonrpc-core", version = "14.0.3" } -sp-version = { version = "2.0.0-alpha.2", path = "../../primitives/version" } +sp-version = { version = "2.0.0-alpha.5", path = "../../primitives/version" } serde_json = "1.0.41" -sp-session = { version = "2.0.0-alpha.2", path = "../../primitives/session" } -sp-offchain = { version = "2.0.0-alpha.2", path = "../../primitives/offchain" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sp-rpc = { version = "2.0.0-alpha.2", path = "../../primitives/rpc" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../primitives/state-machine" } -sc-executor = { version = "0.8.0-alpha.2", path = "../executor" } -sc-block-builder = { version = "0.8.0-alpha.2", path = "../../client/block-builder" } -sc-keystore = { version = "2.0.0-alpha.2", path = "../keystore" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../primitives/transaction-pool" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } +sp-session = { version = "2.0.0-alpha.5", path = "../../primitives/session" } +sp-offchain = { version = "2.0.0-alpha.5", path = "../../primitives/offchain" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sp-rpc = { version = "2.0.0-alpha.5", path = "../../primitives/rpc" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../primitives/state-machine" } +sc-executor = { version = "0.8.0-alpha.5", path = "../executor" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "../../client/block-builder" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../keystore" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../primitives/transaction-pool" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } hash-db = { version = "0.15.2", default-features = false } parking_lot = "0.10.0" [dev-dependencies] assert_matches = "1.3.0" futures01 = { package = "futures", version = "0.1.29" } -sc-network = { version = "0.8.0-alpha.2", path = "../network" } -rustc-hex = "2.0.1" -sp-io = { version = "2.0.0-alpha.2", path = "../../primitives/io" } +sc-network = { version = "0.8.0-alpha.5", path = "../network" } +sp-io = { version = "2.0.0-alpha.5", path = "../../primitives/io" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client" } tokio = "0.1.22" -sc-transaction-pool = { version = "2.0.0-alpha.2", path = "../transaction-pool" } +sc-transaction-pool = { version = "2.0.0-alpha.5", path = "../transaction-pool" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/rpc/src/author/mod.rs b/client/rpc/src/author/mod.rs index 80a3a4349ed82f5fa2ebe04d96cf24d28e18ae5e..a3f23e8e1437a146932410d08ce6835b40907a6d 100644 --- a/client/rpc/src/author/mod.rs +++ b/client/rpc/src/author/mod.rs @@ -37,7 +37,7 @@ use sp_core::{Bytes, traits::BareCryptoStorePtr}; use sp_api::ProvideRuntimeApi; use sp_runtime::generic; use sp_transaction_pool::{ - TransactionPool, InPoolTransaction, TransactionStatus, + TransactionPool, InPoolTransaction, TransactionStatus, TransactionSource, BlockHash, TxHash, TransactionFor, error::IntoPoolError, }; use sp_session::SessionKeys; @@ -75,6 +75,14 @@ impl Author { } } + +/// 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; + impl AuthorApi, BlockHash

> for Author where P: TransactionPool + Sync + Send + 'static, @@ -127,7 +135,7 @@ impl AuthorApi, BlockHash

> for Author }; let best_block_hash = self.client.info().best_hash; Box::new(self.pool - .submit_one(&generic::BlockId::hash(best_block_hash), xt) + .submit_one(&generic::BlockId::hash(best_block_hash), TX_SOURCE, xt) .compat() .map_err(|e| e.into_pool_error() .map(Into::into) @@ -173,7 +181,7 @@ impl AuthorApi, BlockHash

> for Author .map_err(error::Error::from)?; Ok( self.pool - .submit_and_watch(&generic::BlockId::hash(best_block_hash), dxt) + .submit_and_watch(&generic::BlockId::hash(best_block_hash), TX_SOURCE, dxt) .map_err(|e| e.into_pool_error() .map(error::Error::from) .unwrap_or_else(|e| error::Error::Verification(Box::new(e)).into()) diff --git a/client/rpc/src/author/tests.rs b/client/rpc/src/author/tests.rs index 3093cd9d3b759b08d887eb10165e26f697a9a073..8b956c23a5e660cd685c9d6381395068decb4ebc 100644 --- a/client/rpc/src/author/tests.rs +++ b/client/rpc/src/author/tests.rs @@ -21,7 +21,8 @@ use assert_matches::assert_matches; use codec::Encode; use sp_core::{ H256, blake2_256, hexdisplay::HexDisplay, testing::{ED25519, SR25519, KeyStore}, - traits::BareCryptoStorePtr, ed25519, crypto::{Pair, Public}, + traits::BareCryptoStorePtr, ed25519, sr25519, + crypto::{CryptoTypePublicPair, Pair, Public}, }; use rpc::futures::Stream as _; use substrate_test_runtime_client::{ @@ -173,7 +174,7 @@ fn should_return_pending_extrinsics() { let ex = uxt(AccountKeyring::Alice, 0); AuthorApi::submit_extrinsic(&p, ex.encode().into()).wait().unwrap(); - assert_matches!( + assert_matches!( p.pending_extrinsics(), Ok(ref expected) if *expected == vec![Bytes(ex.encode())] ); @@ -199,7 +200,7 @@ fn should_remove_extrinsics() { hash::ExtrinsicOrHash::Extrinsic(ex1.encode().into()), ]).unwrap(); - assert_eq!(removed.len(), 3); + assert_eq!(removed.len(), 3); } #[test] @@ -215,10 +216,9 @@ fn should_insert_key() { key_pair.public().0.to_vec().into(), ).expect("Insert key"); - let store_key_pair = setup.keystore.read() - .ed25519_key_pair(ED25519, &key_pair.public()).expect("Key exists in store"); + let public_keys = setup.keystore.read().keys(ED25519).unwrap(); - assert_eq!(key_pair.public(), store_key_pair.public()); + assert!(public_keys.contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, key_pair.public().to_raw_vec()))); } #[test] @@ -231,18 +231,11 @@ fn should_rotate_keys() { let session_keys = SessionKeys::decode(&mut &new_public_keys[..]) .expect("SessionKeys decode successfully"); - let ed25519_key_pair = setup.keystore.read().ed25519_key_pair( - ED25519, - &session_keys.ed25519.clone().into(), - ).expect("ed25519 key exists in store"); - - let sr25519_key_pair = setup.keystore.read().sr25519_key_pair( - SR25519, - &session_keys.sr25519.clone().into(), - ).expect("sr25519 key exists in store"); + let ed25519_public_keys = setup.keystore.read().keys(ED25519).unwrap(); + let sr25519_public_keys = setup.keystore.read().keys(SR25519).unwrap(); - assert_eq!(session_keys.ed25519, ed25519_key_pair.public().into()); - assert_eq!(session_keys.sr25519, sr25519_key_pair.public().into()); + assert!(ed25519_public_keys.contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, session_keys.ed25519.to_raw_vec()))); + assert!(sr25519_public_keys.contains(&CryptoTypePublicPair(sr25519::CRYPTO_ID, session_keys.sr25519.to_raw_vec()))); } #[test] diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index 82568866ee3ba4df6686d9201783f7cf8cfa673e..2747405c04fcac0c7534ea802bb5e1f08a7e1911 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -163,6 +163,13 @@ pub trait StateBackend: Send + Sync + 'static keys: Vec, ) -> FutureResult>>; + /// Query storage entries (by key) starting at block hash given as the second parameter. + fn query_storage_at( + &self, + keys: Vec, + at: Option + ) -> FutureResult>>; + /// New runtime version subscription fn subscribe_runtime_version( &self, @@ -357,6 +364,14 @@ impl StateApi for State self.backend.query_storage(from, to, keys) } + fn query_storage_at( + &self, + keys: Vec, + at: Option + ) -> FutureResult>> { + self.backend.query_storage_at(keys, at) + } + fn subscribe_storage( &self, meta: Self::Metadata, diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index b7589d2aefecafc9daa41e2122461c21f53c93d2..bf80820543102483ae5c41a301ad1ab0d322890d 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -33,7 +33,7 @@ use sp_core::{ }; use sp_version::RuntimeVersion; use sp_runtime::{ - generic::BlockId, traits::{Block as BlockT, NumberFor, SaturatedConversion}, + generic::BlockId, traits::{Block as BlockT, NumberFor, SaturatedConversion, CheckedSub}, }; use sp_api::{Metadata, ProvideRuntimeApi, CallApiAt}; @@ -94,8 +94,8 @@ impl FullState let from_meta = self.client.header_metadata(from).map_err(invalid_block_err)?; let to_meta = self.client.header_metadata(to).map_err(invalid_block_err)?; - if from_meta.number >= to_meta.number { - return Err(invalid_block_range(&from_meta, &to_meta, "from number >= to number".to_owned())) + if from_meta.number > to_meta.number { + return Err(invalid_block_range(&from_meta, &to_meta, "from number > to number".to_owned())) } // check if we can get from `to` to `from` by going through parent_hashes. @@ -122,7 +122,10 @@ impl FullState .max_key_changes_range(from_number, BlockId::Hash(to_meta.hash)) .map_err(client_err)?; let filtered_range_begin = changes_trie_range - .map(|(begin, _)| (begin - from_number).saturated_into::()); + .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 { @@ -398,6 +401,15 @@ impl StateBackend for FullState, + at: Option + ) -> FutureResult>> { + let at = at.unwrap_or_else(|| self.client.info().best_hash); + self.query_storage(at, Some(at), keys) + } + fn subscribe_runtime_version( &self, _meta: crate::metadata::Metadata, diff --git a/client/rpc/src/state/state_light.rs b/client/rpc/src/state/state_light.rs index 59c0f2183cf898f0677ec9e39b843378244a7fda..092419ad0129e6f4109a19d3f916dc0eb24f5738 100644 --- a/client/rpc/src/state/state_light.rs +++ b/client/rpc/src/state/state_light.rs @@ -331,6 +331,14 @@ impl StateBackend for LightState, + _at: Option + ) -> FutureResult>> { + Box::new(result(Err(client_err(ClientError::NotAvailableOnLightClient)))) + } + fn subscribe_storage( &self, _meta: crate::metadata::Metadata, diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index 75ce4ed9c3c4d7e89a4bfb8d691e9a06d2654c7c..4a9b701959c8c292a3b4dba25d89762fedfd8f62 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -30,6 +30,7 @@ use substrate_test_runtime_client::{ sp_consensus::BlockOrigin, runtime, }; +use sp_runtime::generic::BlockId; const CHILD_INFO: ChildInfo<'static> = ChildInfo::new_default(b"unique_id"); @@ -212,7 +213,7 @@ fn should_send_initial_storage_changes_and_notifications() { #[test] fn should_query_storage() { - fn run_tests(mut client: Arc) { + fn run_tests(mut client: Arc, has_changes_trie_config: bool) { let core = tokio::runtime::Runtime::new().unwrap(); let api = new_full(client.clone(), Subscriptions::new(Arc::new(core.executor()))); @@ -237,6 +238,13 @@ 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, @@ -306,7 +314,7 @@ fn should_query_storage() { Err(Error::InvalidBlockRange { from: format!("1 ({:?})", block1_hash), to: format!("0 ({:?})", genesis_hash), - details: "from number >= to number".to_owned(), + details: "from number > to number".to_owned(), }).map_err(|e| e.to_string()) ); @@ -376,12 +384,39 @@ fn should_query_storage() { details: format!("UnknownBlock: header not found in db: {}", random_hash1), }).map_err(|e| e.to_string()), ); + + // single block range + let result = api.query_storage_at( + keys.clone(), + Some(block1_hash), + ); + + assert_eq!( + result.wait().unwrap(), + vec![ + StorageChangeSet { + block: block1_hash, + changes: vec![ + (StorageKey(vec![1_u8]), None), + (StorageKey(vec![2_u8]), Some(StorageData(vec![2_u8]))), + (StorageKey(vec![3_u8]), Some(StorageData(vec![3_u8]))), + (StorageKey(vec![4_u8]), None), + (StorageKey(vec![5_u8]), Some(StorageData(vec![0_u8]))), + ] + } + ] + ); } - run_tests(Arc::new(substrate_test_runtime_client::new())); - run_tests(Arc::new(TestClientBuilder::new() - .changes_trie_config(Some(ChangesTrieConfiguration::new(4, 2))) - .build())); + 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] @@ -403,7 +438,7 @@ fn should_return_runtime_version() { let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ \"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",2],\ - [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",1],[\"0x40fe3ad401f8959a\",4],\ + [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",2],[\"0x40fe3ad401f8959a\",4],\ [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",1],\ [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]]}"; diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index ba376ed329b9dc4fb72594077012bed3feede30b..3811ea079daef8a337e6659528d05b48a1c479b5 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-service" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -20,7 +20,7 @@ wasmtime = [ [dependencies] derive_more = "0.99.2" futures01 = { package = "futures", version = "0.1.29" } -futures = "0.3.1" +futures = "0.3.4" futures-diagnose = "1.0" parking_lot = "0.10.0" lazy_static = "1.4.0" @@ -31,38 +31,41 @@ wasm-timer = "0.2" exit-future = "0.2.0" serde = "1.0.101" serde_json = "1.0.41" -sysinfo = "0.11.7" +sysinfo = "0.12.0" target_info = "0.1.0" -sc-keystore = { version = "2.0.0-alpha.2", path = "../keystore" } -sp-io = { version = "2.0.0-alpha.2", path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-session = { version = "2.0.0-alpha.2", path = "../../primitives/session" } -sp-application-crypto = { version = "2.0.0-alpha.2", path = "../../primitives/application-crypto" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../primitives/consensus/common" } -sc-network = { version = "0.8.0-alpha.2", path = "../network" } -sc-chain-spec = { version = "2.0.0-alpha.2", path = "../chain-spec" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sc-client = { version = "0.8.0-alpha.2", path = "../" } -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api" } -sc-client-db = { version = "0.8.0-alpha.2", path = "../db" } -codec = { package = "parity-scale-codec", version = "1.2.0" } -sc-executor = { version = "0.8.0-alpha.2", path = "../executor" } -sc-transaction-pool = { version = "2.0.0-alpha.2", path = "../transaction-pool" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../primitives/transaction-pool" } -sc-rpc-server = { version = "2.0.0-alpha.2", path = "../rpc-servers" } -sc-rpc = { version = "2.0.0-alpha.2", path = "../rpc" } -sc-telemetry = { version = "2.0.0-alpha.2", path = "../telemetry" } -sc-offchain = { version = "2.0.0-alpha.2", path = "../offchain" } +sc-keystore = { version = "2.0.0-alpha.5", path = "../keystore" } +sp-io = { version = "2.0.0-alpha.5", path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-session = { version = "2.0.0-alpha.5", path = "../../primitives/session" } +sp-application-crypto = { version = "2.0.0-alpha.5", path = "../../primitives/application-crypto" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/common" } +sc-network = { version = "0.8.0-alpha.5", path = "../network" } +sc-chain-spec = { version = "2.0.0-alpha.5", path = "../chain-spec" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sc-client = { version = "0.8.0-alpha.5", path = "../" } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api" } +sc-client-db = { version = "0.8.0-alpha.5", path = "../db" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +sc-executor = { version = "0.8.0-alpha.5", path = "../executor" } +sc-transaction-pool = { version = "2.0.0-alpha.5", path = "../transaction-pool" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../primitives/transaction-pool" } +sc-rpc-server = { version = "2.0.0-alpha.5", path = "../rpc-servers" } +sc-rpc = { version = "2.0.0-alpha.5", path = "../rpc" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../telemetry" } +sc-offchain = { version = "2.0.0-alpha.5", path = "../offchain" } parity-multiaddr = { package = "parity-multiaddr", version = "0.7.3" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" , version = "0.8.0-alpha.2"} -sc-tracing = { version = "2.0.0-alpha.2", path = "../tracing" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" , version = "0.8.0-alpha.5"} +sc-tracing = { version = "2.0.0-alpha.5", path = "../tracing" } tracing = "0.1.10" -parity-util-mem = { version = "0.5.2", default-features = false, features = ["primitive-types"] } +parity-util-mem = { version = "0.6.0", default-features = false, features = ["primitive-types"] } [dev-dependencies] substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client" } -sp-consensus-babe = { version = "0.8.0-alpha.2", path = "../../primitives/consensus/babe" } -grandpa = { version = "0.8.0-alpha.2", package = "sc-finality-grandpa", path = "../finality-grandpa" } -grandpa-primitives = { version = "2.0.0-alpha.2", package = "sp-finality-grandpa", path = "../../primitives/finality-grandpa" } +sp-consensus-babe = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/babe" } +grandpa = { version = "0.8.0-alpha.5", package = "sc-finality-grandpa", path = "../finality-grandpa" } +grandpa-primitives = { version = "2.0.0-alpha.5", package = "sp-finality-grandpa", path = "../../primitives/finality-grandpa" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 4363e204c07a8350a96c474ca2f7e6906411e911..6f8610a6128253b49c78d7bde6bdc83f34498515 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -77,7 +77,7 @@ impl ServiceMetrics { "ready_transactions_number", "Number of transactions in the ready queue", )?, registry)?, memory_usage_bytes: register(Gauge::new( - "memory_usage_bytes", "Node memory usage", + "memory_usage_bytes", "Node memory (resident set size) usage", )?, registry)?, cpu_usage_percentage: register(Gauge::new( "cpu_usage_percentage", "Node CPU usage", @@ -827,7 +827,7 @@ ServiceBuilder< let chain_spec = config.expect_chain_spec(); let version = config.full_version(); - info!("Highest known block at #{}", chain_info.best_number); + info!("📦 Highest known block at #{}", chain_info.best_number); telemetry!( SUBSTRATE_INFO; "node.start"; @@ -1074,7 +1074,8 @@ ServiceBuilder< .unwrap_or(0), ); if let Some(metrics) = metrics.as_ref() { - metrics.memory_usage_bytes.set(memory); + // `sysinfo::Process::memory` returns memory usage in KiB and not bytes. + metrics.memory_usage_bytes.set(memory * 1024); metrics.cpu_usage_percentage.set(f64::from(cpu_usage)); metrics.ready_transactions_number.set(txpool_status.ready as u64); @@ -1203,6 +1204,7 @@ ServiceBuilder< network_status_sinks.clone(), system_rpc_rx, has_bootnodes, + config.announce_block, ), ); diff --git a/client/service/src/chain_ops.rs b/client/service/src/chain_ops.rs index c58a2755ddc51eeb4e346a5f3c993edbfd5cf578..12fae3224108a66feed5e593e8de50bb97c8a9cf 100644 --- a/client/service/src/chain_ops.rs +++ b/client/service/src/chain_ops.rs @@ -125,7 +125,7 @@ impl< return std::task::Poll::Ready(Err(From::from(err))); }, }; - info!("Importing {} blocks", c); + info!("📦 Importing {} blocks", c); count = Some(c); c } @@ -185,7 +185,7 @@ impl< } if link.imported_blocks >= count { - info!("Imported {} blocks. Best: #{}", read_block_count, client.chain_info().best_number); + info!("🎉 Imported {} blocks. Best: #{}", read_block_count, client.chain_info().best_number); return std::task::Poll::Ready(Ok(())); } else { diff --git a/client/service/src/config.rs b/client/service/src/config.rs index d9d497d1e999f219b95983f65f272137d94d7031..7eb1d501cefc8e0ffbf44b6e09d3c168d78b3823 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -127,6 +127,8 @@ pub struct Configuration { /// /// The default value is 8. pub max_runtime_instances: usize, + /// Announce block automatically after they have been imported + pub announce_block: bool, } /// Configuration of the client keystore. @@ -229,6 +231,7 @@ impl Default for Configuration { tracing_targets: Default::default(), tracing_receiver: Default::default(), max_runtime_instances: 8, + announce_block: true, } } } diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 0a7d5ff103f3cf2c28abd7577e4a06ff5e8c797d..3a071893d52dbccf5978ae11e7a25b015a824477 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -328,6 +328,7 @@ fn build_network_future< status_sinks: Arc, NetworkState)>>>, mut rpc_rx: mpsc::UnboundedReceiver>, should_have_peers: bool, + announce_imported_blocks: bool, ) -> impl Future { let mut imported_blocks_stream = client.import_notification_stream().fuse(); let mut finality_notification_stream = client.finality_notification_stream().fuse(); @@ -337,7 +338,11 @@ fn build_network_future< // We poll `imported_blocks_stream`. while let Poll::Ready(Some(notification)) = Pin::new(&mut imported_blocks_stream).poll_next(cx) { - network.on_block_imported(notification.header, Vec::new(), notification.is_new_best); + network.on_block_imported(notification.header, notification.is_new_best); + + if announce_imported_blocks { + network.service().announce_block(notification.hash, Vec::new()); + } } // We poll `finality_notification_stream`, but we only take the last event. @@ -621,7 +626,8 @@ where match Decode::decode(&mut &encoded[..]) { Ok(uxt) => { let best_block_id = BlockId::hash(self.client.info().best_hash); - let import_future = self.pool.submit_one(&best_block_id, uxt); + let source = sp_transaction_pool::TransactionSource::External; + let import_future = self.pool.submit_one(&best_block_id, source, uxt); let import_future = import_future .map(move |import_result| { match import_result { @@ -648,7 +654,11 @@ where } fn transaction(&self, hash: &H) -> Option { - self.pool.ready_transaction(hash).map(|tx| tx.data().clone()) + self.pool.ready_transaction(hash) + .and_then( + // Only propagable transactions should be resolved for network service. + |tx| if tx.is_propagable() { Some(tx.data().clone()) } else { None } + ) } } @@ -670,6 +680,7 @@ mod tests { Default::default(), Arc::new(FullChainApi::new(client.clone())), ).0); + let source = sp_runtime::transaction_validity::TransactionSource::External; let best = longest_chain.best_chain().unwrap(); let transaction = Transfer { amount: 5, @@ -677,8 +688,12 @@ mod tests { from: AccountKeyring::Alice.into(), to: Default::default(), }.into_signed_tx(); - block_on(pool.submit_one(&BlockId::hash(best.hash()), transaction.clone())).unwrap(); - block_on(pool.submit_one(&BlockId::hash(best.hash()), Extrinsic::IncludeData(vec![1]))).unwrap(); + block_on(pool.submit_one( + &BlockId::hash(best.hash()), source, transaction.clone()), + ).unwrap(); + block_on(pool.submit_one( + &BlockId::hash(best.hash()), source, Extrinsic::IncludeData(vec![1])), + ).unwrap(); assert_eq!(pool.status().ready, 2); // when diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index f27f8803300b30d86f6ad605541bed438788ffb7..39c17420bf571fc7688b728999f3e302bf6a7f23 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -14,12 +14,15 @@ tokio = "0.1.22" futures01 = { package = "futures", version = "0.1.29" } log = "0.4.8" env_logger = "0.7.0" -fdlimit = "0.1.1" +fdlimit = "0.1.4" futures = { version = "0.3.1", features = ["compat"] } -sc-service = { version = "0.8.0-alpha.2", default-features = false, path = "../../service" } -sc-network = { version = "0.8.0-alpha.2", path = "../../network" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } -sc-client = { version = "0.8.0-alpha.2", path = "../../" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../../primitives/transaction-pool" } +sc-service = { version = "0.8.0-alpha.5", default-features = false, path = "../../service" } +sc-network = { version = "0.8.0-alpha.5", path = "../../network" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } +sc-client = { version = "0.8.0-alpha.5", path = "../../" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../../primitives/transaction-pool" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index 259c25ee4d436cc0b953336d0278cdad2239cbe5..57aed116ef3b4b42c6cc49b30ceb97ca24eff56f 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -131,7 +131,7 @@ where F: Send + 'static, L: Send +'static, U: Clone + Send + 'static } } -fn node_config ( +fn node_config ( index: usize, spec: &GenericChainSpec, role: Roles, @@ -213,13 +213,14 @@ fn node_config TestNet where F: AbstractService, L: AbstractService, - E: ChainSpecExtension + Clone + 'static, + E: ChainSpecExtension + Clone + 'static + Send, G: RuntimeGenesis + 'static, { fn new( @@ -324,7 +325,7 @@ pub fn connectivity( full_builder: Fb, light_builder: Lb, ) where - E: ChainSpecExtension + Clone + 'static, + E: ChainSpecExtension + Clone + 'static + Send, G: RuntimeGenesis + 'static, Fb: Fn(Configuration) -> Result, F: AbstractService, @@ -432,7 +433,7 @@ pub fn sync( B: FnMut(&F, &mut U), ExF: FnMut(&F, &U) -> ::Extrinsic, U: Clone + Send + 'static, - E: ChainSpecExtension + Clone + 'static, + E: ChainSpecExtension + Clone + 'static + Send, G: RuntimeGenesis + 'static, { const NUM_FULL_NODES: usize = 10; @@ -482,7 +483,12 @@ pub fn sync( let first_user_data = &network.full_nodes[0].2; let best_block = BlockId::number(first_service.get().client().chain_info().best_number); let extrinsic = extrinsic_factory(&first_service.get(), first_user_data); - futures::executor::block_on(first_service.get().transaction_pool().submit_one(&best_block, extrinsic)).unwrap(); + let source = sp_transaction_pool::TransactionSource::External; + + futures::executor::block_on( + first_service.get().transaction_pool().submit_one(&best_block, source, extrinsic) + ).expect("failed to submit extrinsic"); + network.run_until_all_full( |_index, service| service.get().transaction_pool().ready().count() == 1, |_index, _service| true, @@ -499,7 +505,7 @@ pub fn consensus( F: AbstractService, Lb: Fn(Configuration) -> Result, L: AbstractService, - E: ChainSpecExtension + Clone + 'static, + E: ChainSpecExtension + Clone + 'static + Send, G: RuntimeGenesis + 'static, { const NUM_FULL_NODES: usize = 10; diff --git a/client/src/client.rs b/client/src/client.rs index 5c7fa80291ba909f58463baf573f0d1498146e5d..7ec941ee7ad3b11bf21b08185400f44020d78aca 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -260,7 +260,7 @@ impl Client where backend.begin_state_operation(&mut op, BlockId::Hash(Default::default()))?; let state_root = op.reset_storage(genesis_storage)?; let genesis_block = genesis::construct_genesis_block::(state_root.into()); - info!("Initializing Genesis block/state (state: {}, header-hash: {})", + info!("🔨 Initializing Genesis block/state (state: {}, header-hash: {})", genesis_block.header().state_root(), genesis_block.header().hash() ); diff --git a/client/state-db/Cargo.toml b/client/state-db/Cargo.toml index da62696a152d9ddeeef615e96c28d02a275440ea..9da3c4e1274cd6065999b2a0214280b4b44ce87d 100644 --- a/client/state-db/Cargo.toml +++ b/client/state-db/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-state-db" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,11 +11,14 @@ description = "State database maintenance. Handles canonicalization and pruning [dependencies] parking_lot = "0.10.0" log = "0.4.8" -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } -parity-util-mem = "0.5.2" +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } +parity-util-mem = "0.6" parity-util-mem-derive = "0.1.0" [dev-dependencies] env_logger = "0.7.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 248c90ec68623f0320815fbf25511d07304dc31d..bb7e7e510372bc253ba6fa2130c6f0e1b7d4a31b 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-telemetry" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] description = "Telemetry utils" edition = "2018" @@ -13,7 +13,7 @@ documentation = "https://docs.rs/sc-telemetry" [dependencies] bytes = "0.5" parking_lot = "0.10.0" -futures = "0.3.1" +futures = "0.3.4" futures-timer = "3.0.1" wasm-timer = "0.2.0" libp2p = { version = "0.16.2", default-features = false, features = ["libp2p-websocket"] } @@ -26,3 +26,6 @@ slog-json = { version = "2.3.0", features = ["nested-values"] } slog-scope = "4.1.2" take_mut = "0.2.2" void = "1.0.2" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/telemetry/src/lib.rs b/client/telemetry/src/lib.rs index f8ca6d5c73d41bdd86dff66a2ebbff8dc7d33dbc..6c90d6bbcca9f3a60fc765e056abd908c0ea0636 100644 --- a/client/telemetry/src/lib.rs +++ b/client/telemetry/src/lib.rs @@ -22,7 +22,7 @@ //! `slog_scope::with_logger` followed with `slog_log!`. //! //! Note that you are supposed to only ever use `telemetry!` and not `slog_scope::with_logger` at -//! the moment. Substate may eventually be reworked to get proper `slog` support, including sending +//! the moment. Substrate may eventually be reworked to get proper `slog` support, including sending //! information to the telemetry. //! //! The [`Telemetry`] struct implements `Stream` and must be polled regularly (or sent to a @@ -41,7 +41,7 @@ //! endpoints: sc_telemetry::TelemetryEndpoints::new(vec![ //! // The `0` is the maximum verbosity level of messages to send to this endpoint. //! ("wss://example.com".into(), 0) -//! ]), +//! ]).expect("Invalid URL or multiaddr provided"), //! // Can be used to pass an external implementation of WebSockets. //! wasm_external_transport: None, //! }); @@ -62,7 +62,7 @@ use futures::{prelude::*, channel::mpsc}; use libp2p::{Multiaddr, wasm_ext}; use log::{error, warn}; use parking_lot::Mutex; -use serde::{Serialize, Deserialize}; +use serde::{Serialize, Deserialize, Deserializer}; use std::{pin::Pin, sync::Arc, task::{Context, Poll}, time::Duration}; use wasm_timer::Instant; @@ -96,12 +96,47 @@ pub struct TelemetryConfig { /// /// The URL string can be either a URL or a multiaddress. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct TelemetryEndpoints(Vec<(String, u8)>); +pub struct TelemetryEndpoints( + #[serde(deserialize_with = "url_or_multiaddr_deser")] + Vec<(Multiaddr, u8)> +); + +/// Custom deserializer for TelemetryEndpoints, used to convert urls or multiaddr to multiaddr. +fn url_or_multiaddr_deser<'de, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de> +{ + Vec::<(String, u8)>::deserialize(deserializer)? + .iter() + .map(|e| Ok((url_to_multiaddr(&e.0) + .map_err(serde::de::Error::custom)?, e.1))) + .collect() +} impl TelemetryEndpoints { - pub fn new(endpoints: Vec<(String, u8)>) -> Self { - TelemetryEndpoints(endpoints) + pub fn new(endpoints: Vec<(String, u8)>) -> Result { + let endpoints: Result, libp2p::multiaddr::Error> = endpoints.iter() + .map(|e| Ok((url_to_multiaddr(&e.0)?, e.1))) + .collect(); + endpoints.map(Self) + } +} + +/// Parses a WebSocket URL into a libp2p `Multiaddr`. +fn url_to_multiaddr(url: &str) -> Result { + // First, assume that we have a `Multiaddr`. + let parse_error = match url.parse() { + Ok(ma) => return Ok(ma), + Err(err) => err, + }; + + // If not, try the `ws://path/url` format. + if let Ok(ma) = libp2p::multiaddr::from_url(url) { + return Ok(ma) } + + // If we have no clue about the format of that string, assume that we were expecting a + // `Multiaddr`. + Err(parse_error) } /// Log levels. @@ -149,13 +184,7 @@ struct TelemetryDrain { /// doesn't provide any way of knowing whether a global logger has already been registered. pub fn init_telemetry(config: TelemetryConfig) -> Telemetry { // Build the list of telemetry endpoints. - let mut endpoints = Vec::new(); - for &(ref url, verbosity) in &config.endpoints.0 { - match url_to_multiaddr(url) { - Ok(addr) => endpoints.push((addr, verbosity)), - Err(err) => warn!(target: "telemetry", "Invalid telemetry URL {}: {}", url, err), - } - } + let (endpoints, wasm_external_transport) = (config.endpoints.0, config.wasm_external_transport); let (sender, receiver) = mpsc::channel(16); let guard = { @@ -164,7 +193,7 @@ pub fn init_telemetry(config: TelemetryConfig) -> Telemetry { slog_scope::set_global_logger(root) }; - let worker = match worker::TelemetryWorker::new(endpoints, config.wasm_external_transport) { + let worker = match worker::TelemetryWorker::new(endpoints, wasm_external_transport) { Ok(w) => Some(w), Err(err) => { error!(target: "telemetry", "Failed to initialize telemetry worker: {:?}", err); @@ -271,24 +300,6 @@ impl slog::Drain for TelemetryDrain { } } -/// Parses a WebSocket URL into a libp2p `Multiaddr`. -fn url_to_multiaddr(url: &str) -> Result { - // First, assume that we have a `Multiaddr`. - let parse_error = match url.parse() { - Ok(ma) => return Ok(ma), - Err(err) => err, - }; - - // If not, try the `ws://path/url` format. - if let Ok(ma) = libp2p::multiaddr::from_url(url) { - return Ok(ma) - } - - // If we have no clue about the format of that string, assume that we were expecting a - // `Multiaddr`. - Err(parse_error) -} - /// Translates to `slog_scope::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. @@ -300,3 +311,35 @@ macro_rules! telemetry { }) } } + +#[cfg(test)] +mod telemetry_endpoints_tests { + use libp2p::Multiaddr; + use super::TelemetryEndpoints; + use super::url_to_multiaddr; + + #[test] + fn valid_endpoints() { + let endp = vec![("wss://telemetry.polkadot.io/submit/".into(), 3), ("/ip4/80.123.90.4/tcp/5432".into(), 4)]; + let telem = TelemetryEndpoints::new(endp.clone()).expect("Telemetry endpoint should be valid"); + let mut res: Vec<(Multiaddr, u8)> = vec![]; + for (a, b) in endp.iter() { + res.push((url_to_multiaddr(a).expect("provided url should be valid"), *b)) + } + assert_eq!(telem.0, res); + } + + #[test] + fn invalid_endpoints() { + let endp = vec![("/ip4/...80.123.90.4/tcp/5432".into(), 3), ("/ip4/no:!?;rlkqre;;::::///tcp/5432".into(), 4)]; + let telem = TelemetryEndpoints::new(endp); + assert!(telem.is_err()); + } + + #[test] + fn valid_and_invalid_endpoints() { + let endp = vec![("/ip4/80.123.90.4/tcp/5432".into(), 3), ("/ip4/no:!?;rlkqre;;::::///tcp/5432".into(), 4)]; + let telem = TelemetryEndpoints::new(endp); + assert!(telem.is_err()); + } +} diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index ad988f0607953fb921d443c15315741dd1820054..319526b61044858240908719d233aaef5a134f98 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-tracing" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" license = "GPL-3.0" authors = ["Parity Technologies "] edition = "2018" @@ -17,7 +17,10 @@ serde_json = "1.0.41" slog = { version = "2.5.2", features = ["nested-values"] } tracing-core = "0.1.7" -sc-telemetry = { version = "2.0.0-alpha.2", path = "../telemetry" } +sc-telemetry = { version = "2.0.0-alpha.5", path = "../telemetry" } [dev-dependencies] tracing = "0.1.10" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/transaction-pool/Cargo.toml b/client/transaction-pool/Cargo.toml index 233397262a04b9dd67be56a6168eb4785224b914..dee1d23fcbfb25839e5478046edd0e4889f31ecd 100644 --- a/client/transaction-pool/Cargo.toml +++ b/client/transaction-pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-transaction-pool" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,24 +9,29 @@ repository = "https://github.com/paritytech/substrate/" description = "Substrate transaction pool implementation." [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0" } +codec = { package = "parity-scale-codec", version = "1.3.0" } derive_more = "0.99.2" futures = { version = "0.3.1", features = ["compat"] } futures-diagnose = "1.0" log = "0.4.8" parking_lot = "0.10.0" wasm-timer = "0.2" -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sc-transaction-graph = { version = "2.0.0-alpha.2", path = "./graph" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../primitives/transaction-pool" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../api" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -futures-timer = "2.0" -parity-util-mem = { version = "0.5.2", default-features = false, features = ["primitive-types"] } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sc-transaction-graph = { version = "2.0.0-alpha.5", path = "./graph" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../primitives/transaction-pool" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../api" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +intervalier = "0.3.1" +parity-util-mem = { version = "0.6.0", default-features = false, features = ["primitive-types"] } [dev-dependencies] -sp-keyring = { version = "2.0.0-alpha.2", path = "../../primitives/keyring" } +assert_matches = "1.3.0" +hex = "0.4" +sp-keyring = { version = "2.0.0-alpha.5", path = "../../primitives/keyring" } substrate-test-runtime-transaction-pool = { version = "2.0.0-dev", path = "../../test-utils/runtime/transaction-pool" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/transaction-pool/graph/Cargo.toml b/client/transaction-pool/graph/Cargo.toml index 080061dd256ff145d0f70285d7afad6a50658752..a88ce6a2e4d007dc96963c5b151f83641a5f0a49 100644 --- a/client/transaction-pool/graph/Cargo.toml +++ b/client/transaction-pool/graph/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-transaction-graph" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,24 +10,27 @@ description = "Generic Transaction Pool" [dependencies] derive_more = "0.99.2" -futures = "0.3.1" +futures = "0.3.4" log = "0.4.8" parking_lot = "0.10.0" serde = { version = "1.0.101", features = ["derive"] } wasm-timer = "0.2" -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../../primitives/transaction-pool" } -parity-util-mem = { version = "0.5.2", default-features = false, features = ["primitive-types"] } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../../primitives/transaction-pool" } +parity-util-mem = { version = "0.6.0", default-features = false, features = ["primitive-types"] } linked-hash-map = "0.5.2" [dev-dependencies] assert_matches = "1.3.0" -codec = { package = "parity-scale-codec", version = "1.2.0" } +codec = { package = "parity-scale-codec", version = "1.3.0" } substrate-test-runtime = { version = "2.0.0-dev", path = "../../../test-utils/runtime" } criterion = "0.3" [[bench]] name = "basics" harness = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/transaction-pool/graph/benches/basics.rs b/client/transaction-pool/graph/benches/basics.rs index 6f5d39f09f5c540a31762cd129e89f48bb4c66d1..23b4dba3488061429a39c04bcd717b55ee007c78 100644 --- a/client/transaction-pool/graph/benches/basics.rs +++ b/client/transaction-pool/graph/benches/basics.rs @@ -18,12 +18,14 @@ use criterion::{criterion_group, criterion_main, Criterion}; use futures::{future::{ready, Ready}, executor::block_on}; use sc_transaction_graph::*; -use sp_runtime::transaction_validity::{ValidTransaction, InvalidTransaction}; use codec::Encode; use substrate_test_runtime::{Block, Extrinsic, Transfer, H256, AccountId}; use sp_runtime::{ generic::BlockId, - transaction_validity::{TransactionValidity, TransactionTag as Tag}, + transaction_validity::{ + ValidTransaction, InvalidTransaction, TransactionValidity, TransactionTag as Tag, + TransactionSource, + }, }; use sp_core::blake2_256; @@ -55,6 +57,7 @@ impl ChainApi for TestApi { fn validate_transaction( &self, at: &BlockId, + _source: TransactionSource, uxt: ExtrinsicFor, ) -> Self::ValidationFuture { let nonce = uxt.transfer().nonce; @@ -121,6 +124,7 @@ fn uxt(transfer: Transfer) -> Extrinsic { } fn bench_configured(pool: Pool, number: u64) { + let source = TransactionSource::External; let mut futures = Vec::new(); let mut tags = Vec::new(); @@ -133,7 +137,7 @@ fn bench_configured(pool: Pool, number: u64) { }); tags.push(to_tag(nonce, AccountId::from_h256(H256::from_low_u64_be(1)))); - futures.push(pool.submit_one(&BlockId::Number(1), xt)); + futures.push(pool.submit_one(&BlockId::Number(1), source, xt)); } let res = block_on(futures::future::join_all(futures.into_iter())); diff --git a/client/transaction-pool/graph/src/base_pool.rs b/client/transaction-pool/graph/src/base_pool.rs index 33b7a51f9415b5bc04a288e3a773e817178b4130..38151e9bfd23b87b3fd4511022eaa7f9953ab46a 100644 --- a/client/transaction-pool/graph/src/base_pool.rs +++ b/client/transaction-pool/graph/src/base_pool.rs @@ -33,6 +33,7 @@ use sp_runtime::transaction_validity::{ TransactionTag as Tag, TransactionLongevity as Longevity, TransactionPriority as Priority, + TransactionSource as Source, }; use sp_transaction_pool::{error, PoolStatus, InPoolTransaction}; @@ -102,6 +103,8 @@ pub struct Transaction { pub provides: Vec, /// Should that transaction be propagated. pub propagate: bool, + /// Source of that transaction. + pub source: Source, } impl AsRef for Transaction { @@ -155,6 +158,7 @@ impl Transaction { bytes: self.bytes.clone(), hash: self.hash.clone(), priority: self.priority.clone(), + source: self.source, valid_till: self.valid_till.clone(), requires: self.requires.clone(), provides: self.provides.clone(), @@ -185,6 +189,7 @@ impl fmt::Debug for Transaction where write!(fmt, "valid_till: {:?}, ", &self.valid_till)?; write!(fmt, "bytes: {:?}, ", &self.bytes)?; write!(fmt, "propagate: {:?}, ", &self.propagate)?; + write!(fmt, "source: {:?}, ", &self.source)?; write!(fmt, "requires: [")?; print_tags(fmt, &self.requires)?; write!(fmt, "], provides: [")?; @@ -556,6 +561,7 @@ mod tests { requires: vec![], provides: vec![vec![1]], propagate: true, + source: Source::External, }).unwrap(); // then @@ -578,6 +584,7 @@ mod tests { requires: vec![], provides: vec![vec![1]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![1u8], @@ -588,6 +595,7 @@ mod tests { requires: vec![], provides: vec![vec![1]], propagate: true, + source: Source::External, }).unwrap_err(); // then @@ -611,6 +619,7 @@ mod tests { requires: vec![vec![0]], provides: vec![vec![1]], propagate: true, + source: Source::External, }).unwrap(); assert_eq!(pool.ready().count(), 0); assert_eq!(pool.ready.len(), 0); @@ -623,6 +632,7 @@ mod tests { requires: vec![], provides: vec![vec![0]], propagate: true, + source: Source::External, }).unwrap(); // then @@ -645,6 +655,7 @@ mod tests { requires: vec![vec![0]], provides: vec![vec![1]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![3u8], @@ -655,6 +666,7 @@ mod tests { requires: vec![vec![2]], provides: vec![], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![2u8], @@ -665,6 +677,7 @@ mod tests { requires: vec![vec![1]], provides: vec![vec![3], vec![2]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![4u8], @@ -675,6 +688,7 @@ mod tests { requires: vec![vec![3], vec![4]], provides: vec![], propagate: true, + source: Source::External, }).unwrap(); assert_eq!(pool.ready().count(), 0); assert_eq!(pool.ready.len(), 0); @@ -688,6 +702,7 @@ mod tests { requires: vec![], provides: vec![vec![0], vec![4]], propagate: true, + source: Source::External, }).unwrap(); // then @@ -720,6 +735,7 @@ mod tests { requires: vec![vec![0]], provides: vec![vec![1]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![3u8], @@ -730,6 +746,7 @@ mod tests { requires: vec![vec![1]], provides: vec![vec![2]], propagate: true, + source: Source::External, }).unwrap(); assert_eq!(pool.ready().count(), 0); assert_eq!(pool.ready.len(), 0); @@ -744,6 +761,7 @@ mod tests { requires: vec![vec![2]], provides: vec![vec![0]], propagate: true, + source: Source::External, }).unwrap(); // then @@ -764,6 +782,7 @@ mod tests { requires: vec![], provides: vec![vec![0]], propagate: true, + source: Source::External, }).unwrap(); let mut it = pool.ready().into_iter().map(|tx| tx.data[0]); assert_eq!(it.next(), Some(4)); @@ -792,6 +811,7 @@ mod tests { requires: vec![vec![0]], provides: vec![vec![1]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![3u8], @@ -802,6 +822,7 @@ mod tests { requires: vec![vec![1]], provides: vec![vec![2]], propagate: true, + source: Source::External, }).unwrap(); assert_eq!(pool.ready().count(), 0); assert_eq!(pool.ready.len(), 0); @@ -816,6 +837,7 @@ mod tests { requires: vec![vec![2]], provides: vec![vec![0]], propagate: true, + source: Source::External, }).unwrap(); // then @@ -836,6 +858,7 @@ mod tests { requires: vec![], provides: vec![vec![0]], propagate: true, + source: Source::External, }).unwrap_err(); let mut it = pool.ready().into_iter().map(|tx| tx.data[0]); assert_eq!(it.next(), None); @@ -859,6 +882,7 @@ mod tests { requires: vec![], provides: vec![vec![0], vec![4]], propagate: true, + source: Source::External, }).expect("import 1 should be ok"); pool.import(Transaction { data: vec![3u8; 1024], @@ -869,6 +893,7 @@ mod tests { requires: vec![], provides: vec![vec![2], vec![7]], propagate: true, + source: Source::External, }).expect("import 2 should be ok"); assert!(parity_util_mem::malloc_size(&pool) > 5000); @@ -887,6 +912,7 @@ mod tests { requires: vec![], provides: vec![vec![0], vec![4]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![1u8], @@ -897,6 +923,7 @@ mod tests { requires: vec![vec![0]], provides: vec![vec![1]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![3u8], @@ -907,6 +934,7 @@ mod tests { requires: vec![vec![2]], provides: vec![], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![2u8], @@ -917,6 +945,7 @@ mod tests { requires: vec![vec![1]], provides: vec![vec![3], vec![2]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![4u8], @@ -927,6 +956,7 @@ mod tests { requires: vec![vec![3], vec![4]], provides: vec![], propagate: true, + source: Source::External, }).unwrap(); // future pool.import(Transaction { @@ -938,6 +968,7 @@ mod tests { requires: vec![vec![11]], provides: vec![], propagate: true, + source: Source::External, }).unwrap(); assert_eq!(pool.ready().count(), 5); assert_eq!(pool.future.len(), 1); @@ -964,6 +995,7 @@ mod tests { requires: vec![vec![0]], provides: vec![vec![100]], propagate: true, + source: Source::External, }).unwrap(); // ready pool.import(Transaction { @@ -975,6 +1007,7 @@ mod tests { requires: vec![], provides: vec![vec![1]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![2u8], @@ -985,6 +1018,7 @@ mod tests { requires: vec![vec![2]], provides: vec![vec![3]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![3u8], @@ -995,6 +1029,7 @@ mod tests { requires: vec![vec![1]], provides: vec![vec![2]], propagate: true, + source: Source::External, }).unwrap(); pool.import(Transaction { data: vec![4u8], @@ -1005,6 +1040,7 @@ mod tests { requires: vec![vec![3], vec![2]], provides: vec![vec![4]], propagate: true, + source: Source::External, }).unwrap(); assert_eq!(pool.ready().count(), 4); @@ -1040,10 +1076,11 @@ mod tests { requires: vec![vec![3], vec![2]], provides: vec![vec![4]], propagate: true, + source: Source::External, }), "Transaction { \ hash: 4, priority: 1000, valid_till: 64, bytes: 1, propagate: true, \ -requires: [03,02], provides: [04], data: [4]}".to_owned() +source: TransactionSource::External, requires: [03,02], provides: [04], data: [4]}".to_owned() ); } @@ -1058,6 +1095,7 @@ requires: [03,02], provides: [04], data: [4]}".to_owned() requires: vec![vec![3], vec![2]], provides: vec![vec![4]], propagate: true, + source: Source::External, }.is_propagable(), true); assert_eq!(Transaction { @@ -1069,6 +1107,7 @@ requires: [03,02], provides: [04], data: [4]}".to_owned() requires: vec![vec![3], vec![2]], provides: vec![vec![4]], propagate: false, + source: Source::External, }.is_propagable(), false); } @@ -1090,6 +1129,7 @@ requires: [03,02], provides: [04], data: [4]}".to_owned() requires: vec![vec![0]], provides: vec![], propagate: true, + source: Source::External, }); if let Err(error::Error::RejectedFutureTransaction) = err { @@ -1113,6 +1153,7 @@ requires: [03,02], provides: [04], data: [4]}".to_owned() requires: vec![vec![0]], provides: vec![], propagate: true, + source: Source::External, }).unwrap(); // then @@ -1142,6 +1183,7 @@ requires: [03,02], provides: [04], data: [4]}".to_owned() requires: vec![vec![0]], provides: vec![], propagate: true, + source: Source::External, }).unwrap(); flag diff --git a/client/transaction-pool/graph/src/future.rs b/client/transaction-pool/graph/src/future.rs index a84a5fbe689c37aacbba24ed1e6a8c6885961589..76181c837f988586d72ebd3568b4c31a35ac6b8d 100644 --- a/client/transaction-pool/graph/src/future.rs +++ b/client/transaction-pool/graph/src/future.rs @@ -249,6 +249,7 @@ impl FutureTransactions { #[cfg(test)] mod tests { use super::*; + use sp_runtime::transaction_validity::TransactionSource; #[test] fn can_track_heap_size() { @@ -263,6 +264,7 @@ mod tests { 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(), diff --git a/client/transaction-pool/graph/src/pool.rs b/client/transaction-pool/graph/src/pool.rs index f0bf17dcb8dd2ecb2091dc9a799c06e8ac90f879..a7e4ab554d501619f2a1259e215c37efbb593791 100644 --- a/client/transaction-pool/graph/src/pool.rs +++ b/client/transaction-pool/graph/src/pool.rs @@ -31,7 +31,9 @@ use futures::{ use sp_runtime::{ generic::BlockId, traits::{self, SaturatedConversion}, - transaction_validity::{TransactionValidity, TransactionTag as Tag, TransactionValidityError}, + transaction_validity::{ + TransactionValidity, TransactionTag as Tag, TransactionValidityError, TransactionSource, + }, }; use sp_transaction_pool::error; use wasm_timer::Instant; @@ -76,6 +78,7 @@ pub trait ChainApi: Send + Sync { fn validate_transaction( &self, at: &BlockId, + source: TransactionSource, uxt: ExtrinsicFor, ) -> Self::ValidationFuture; @@ -144,12 +147,17 @@ impl Pool { } /// Imports a bunch of unverified extrinsics to the pool - pub async fn submit_at(&self, at: &BlockId, xts: T, force: bool) - -> Result, B::Error>>, B::Error> - where - T: IntoIterator> + pub async fn submit_at( + &self, + at: &BlockId, + source: TransactionSource, + xts: T, + force: bool, + ) -> Result, B::Error>>, B::Error> where + T: IntoIterator>, { let validated_pool = self.validated_pool.clone(); + let xts = xts.into_iter().map(|xt| (source, xt)); self.verify(at, xts, force) .map(move |validated_transactions| validated_transactions .map(|validated_transactions| validated_pool.submit(validated_transactions @@ -162,9 +170,10 @@ impl Pool { pub async fn submit_one( &self, at: &BlockId, + source: TransactionSource, xt: ExtrinsicFor, ) -> Result, B::Error> { - self.submit_at(at, std::iter::once(xt), false) + self.submit_at(at, source, std::iter::once(xt), false) .map(|import_result| import_result.and_then(|mut import_result| import_result .pop() .expect("One extrinsic passed; one result returned; qed") @@ -176,10 +185,13 @@ impl Pool { pub async fn submit_and_watch( &self, at: &BlockId, + source: TransactionSource, xt: ExtrinsicFor, ) -> Result, BlockHash>, B::Error> { let block_number = self.resolve_block_number(at)?; - let (_, tx) = self.verify_one(at, block_number, xt, false).await; + let (_, tx) = self.verify_one( + at, block_number, source, xt, false + ).await; self.validated_pool.submit_and_watch(tx) } @@ -249,7 +261,7 @@ impl Pool { // to get validity info and tags that the extrinsic provides. None => { let validity = self.validated_pool.api() - .validate_transaction(parent, extrinsic.clone()) + .validate_transaction(parent, TransactionSource::InBlock, extrinsic.clone()) .await; if let Ok(Ok(validity)) = validity { @@ -303,8 +315,12 @@ impl Pool { // Try to re-validate pruned transactions since some of them might be still valid. // note that `known_imported_hashes` will be rejected here due to temporary ban. - let pruned_hashes = prune_status.pruned.iter().map(|tx| tx.hash.clone()).collect::>(); - let pruned_transactions = prune_status.pruned.into_iter().map(|tx| tx.data.clone()); + let pruned_hashes = prune_status.pruned + .iter() + .map(|tx| tx.hash.clone()).collect::>(); + let pruned_transactions = prune_status.pruned + .into_iter() + .map(|tx| (tx.source, tx.data.clone())); let reverified_transactions = self.verify(at, pruned_transactions, false).await?; @@ -335,7 +351,7 @@ impl Pool { async fn verify( &self, at: &BlockId, - xts: impl IntoIterator>, + xts: impl IntoIterator)>, force: bool, ) -> Result, ValidatedTransactionFor>, B::Error> { // we need a block number to compute tx validity @@ -345,7 +361,7 @@ impl Pool { for (hash, validated_tx) in futures::future::join_all( xts.into_iter() - .map(|xt| self.verify_one(at, block_number, xt, force)) + .map(|(source, xt)| self.verify_one(at, block_number, source, xt, force)) ) .await { @@ -360,6 +376,7 @@ impl Pool { &self, block_id: &BlockId, block_number: NumberFor, + source: TransactionSource, xt: ExtrinsicFor, force: bool, ) -> (ExHash, ValidatedTransactionFor) { @@ -371,7 +388,11 @@ impl Pool { ) } - let validation_result = self.validated_pool.api().validate_transaction(block_id, xt.clone()).await; + let validation_result = self.validated_pool.api().validate_transaction( + block_id, + source, + xt.clone(), + ).await; let status = match validation_result { Ok(status) => status, @@ -386,6 +407,7 @@ impl Pool { ValidatedTransaction::valid_at( block_number.saturated_into::(), hash.clone(), + source, xt, bytes, validity, @@ -422,7 +444,7 @@ mod tests { use futures::executor::block_on; use super::*; use sp_transaction_pool::TransactionStatus; - use sp_runtime::transaction_validity::{ValidTransaction, InvalidTransaction}; + use sp_runtime::transaction_validity::{ValidTransaction, InvalidTransaction, TransactionSource}; use codec::Encode; use substrate_test_runtime::{Block, Extrinsic, Transfer, H256, AccountId}; use assert_matches::assert_matches; @@ -430,6 +452,7 @@ mod tests { use crate::base_pool::Limit; const INVALID_NONCE: u64 = 254; + const SOURCE: TransactionSource = TransactionSource::External; #[derive(Clone, Debug, Default)] struct TestApi { @@ -450,6 +473,7 @@ mod tests { fn validate_transaction( &self, at: &BlockId, + _source: TransactionSource, uxt: ExtrinsicFor, ) -> Self::ValidationFuture { let hash = self.hash_and_length(&uxt).0; @@ -541,7 +565,7 @@ mod tests { let pool = pool(); // when - let hash = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let hash = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -565,7 +589,7 @@ mod tests { // when pool.validated_pool.rotator().ban(&Instant::now(), vec![pool.hash_of(&uxt)]); - let res = block_on(pool.submit_one(&BlockId::Number(0), 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); @@ -581,20 +605,20 @@ mod tests { let stream = pool.validated_pool().import_notification_stream(); // when - let _hash = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let _hash = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, 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, }))).unwrap(); - let _hash = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let _hash = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, 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: 1, }))).unwrap(); // future doesn't count - let _hash = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let _hash = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -617,19 +641,19 @@ mod tests { fn should_clear_stale_transactions() { // given let pool = pool(); - let hash1 = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let hash1 = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, 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, }))).unwrap(); - let hash2 = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let hash2 = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, 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: 1, }))).unwrap(); - let hash3 = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let hash3 = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -653,7 +677,7 @@ mod tests { fn should_ban_mined_transactions() { // given let pool = pool(); - let hash1 = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let hash1 = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -680,7 +704,7 @@ mod tests { ..Default::default() }, TestApi::default().into()); - let hash1 = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let hash1 = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -689,7 +713,7 @@ mod tests { assert_eq!(pool.validated_pool().status().future, 1); // when - let hash2 = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let hash2 = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(2)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -716,7 +740,7 @@ mod tests { }, TestApi::default().into()); // when - block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -734,7 +758,7 @@ mod tests { let pool = pool(); // when - let err = block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + let err = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -754,7 +778,7 @@ mod tests { fn should_trigger_ready_and_finalized() { // given let pool = pool(); - let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), uxt(Transfer { + let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -778,7 +802,7 @@ mod tests { fn should_trigger_ready_and_finalized_when_pruning_via_hash() { // given let pool = pool(); - let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), uxt(Transfer { + let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -802,7 +826,7 @@ mod tests { fn should_trigger_future_and_ready_after_promoted() { // given let pool = pool(); - let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), uxt(Transfer { + let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -812,7 +836,7 @@ mod tests { assert_eq!(pool.validated_pool().status().future, 1); // when - block_on(pool.submit_one(&BlockId::Number(0), uxt(Transfer { + block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Transfer { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, @@ -836,7 +860,7 @@ mod tests { amount: 5, nonce: 0, }); - let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), uxt)).unwrap(); + let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), SOURCE, uxt)).unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -860,7 +884,7 @@ mod tests { amount: 5, nonce: 0, }); - let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), uxt)).unwrap(); + let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), SOURCE, uxt)).unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -895,7 +919,7 @@ mod tests { amount: 5, nonce: 0, }); - let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), xt)).unwrap(); + let watcher = block_on(pool.submit_and_watch(&BlockId::Number(0), SOURCE, xt)).unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -905,7 +929,7 @@ mod tests { amount: 4, nonce: 1, }); - block_on(pool.submit_one(&BlockId::Number(1), xt)).unwrap(); + block_on(pool.submit_one(&BlockId::Number(1), SOURCE, xt)).unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); // then @@ -934,7 +958,7 @@ mod tests { // This transaction should go to future, since we use `nonce: 1` let pool2 = pool.clone(); std::thread::spawn(move || { - block_on(pool2.submit_one(&BlockId::Number(0), xt)).unwrap(); + block_on(pool2.submit_one(&BlockId::Number(0), SOURCE, xt)).unwrap(); ready.send(()).unwrap(); }); @@ -948,7 +972,7 @@ mod tests { }); // The tag the above transaction provides (TestApi is using just nonce as u8) let provides = vec![0_u8]; - block_on(pool.submit_one(&BlockId::Number(0), xt)).unwrap(); + block_on(pool.submit_one(&BlockId::Number(0), SOURCE, xt)).unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); // Now block import happens before the second transaction is able to finish verification. diff --git a/client/transaction-pool/graph/src/ready.rs b/client/transaction-pool/graph/src/ready.rs index 23f0d49a93071ec180822bef294888a4a6373127..c856535a6165140844a339d0539aa291236a561f 100644 --- a/client/transaction-pool/graph/src/ready.rs +++ b/client/transaction-pool/graph/src/ready.rs @@ -545,6 +545,7 @@ fn remove_item(vec: &mut Vec, item: &T) { #[cfg(test)] mod tests { use super::*; + use sp_runtime::transaction_validity::TransactionSource as Source; fn tx(id: u8) -> Transaction> { Transaction { @@ -556,6 +557,7 @@ mod tests { requires: vec![vec![1], vec![2]], provides: vec![vec![3], vec![4]], propagate: true, + source: Source::External, } } @@ -656,6 +658,7 @@ mod tests { requires: vec![tx1.provides[0].clone()], provides: vec![], propagate: true, + source: Source::External, }; // when @@ -688,6 +691,7 @@ mod tests { requires: vec![], provides: vec![], propagate: true, + source: Source::External, }; import(&mut ready, tx).unwrap(); diff --git a/client/transaction-pool/graph/src/rotator.rs b/client/transaction-pool/graph/src/rotator.rs index 55a9230522e2f7ee19e31cd004184d691ee8fd9e..be96174d1d91696832201bae8d9c6db4d1d0769f 100644 --- a/client/transaction-pool/graph/src/rotator.rs +++ b/client/transaction-pool/graph/src/rotator.rs @@ -100,6 +100,7 @@ impl PoolRotator { #[cfg(test)] mod tests { use super::*; + use sp_runtime::transaction_validity::TransactionSource; type Hash = u64; type Ex = (); @@ -122,6 +123,7 @@ mod tests { requires: vec![], provides: vec![], propagate: true, + source: TransactionSource::External, }; (hash, tx) @@ -188,6 +190,7 @@ mod tests { requires: vec![], provides: vec![], propagate: true, + source: TransactionSource::External, } } diff --git a/client/transaction-pool/graph/src/validated_pool.rs b/client/transaction-pool/graph/src/validated_pool.rs index a62822a91858b3d0bc0023832e0fe82a880116c3..b63e56e48101e6e38780d44b57d0c40f6da48a8e 100644 --- a/client/transaction-pool/graph/src/validated_pool.rs +++ b/client/transaction-pool/graph/src/validated_pool.rs @@ -32,7 +32,7 @@ use parking_lot::{Mutex, RwLock}; use sp_runtime::{ generic::BlockId, traits::{self, SaturatedConversion}, - transaction_validity::{TransactionTag as Tag, ValidTransaction}, + transaction_validity::{TransactionTag as Tag, ValidTransaction, TransactionSource}, }; use sp_transaction_pool::{error, PoolStatus}; use wasm_timer::Instant; @@ -58,6 +58,7 @@ impl ValidatedTransaction { pub fn valid_at( at: u64, hash: Hash, + source: TransactionSource, data: Ex, bytes: usize, validity: ValidTransaction, @@ -66,6 +67,7 @@ impl ValidatedTransaction { data, bytes, hash, + source, priority: validity.priority, requires: validity.requires, provides: validity.provides, @@ -545,7 +547,7 @@ impl ValidatedPool { } /// Get an iterator for ready transactions ordered by priority - pub fn ready(&self) -> impl Iterator> { + pub fn ready(&self) -> impl Iterator> + Send { self.pool.read().ready() } diff --git a/client/transaction-pool/src/api.rs b/client/transaction-pool/src/api.rs index a8888baa4839b46bddd2cb2f2b1486efc324ce33..bf104c8d78191c231b7102e97d31ccdf1237fefa 100644 --- a/client/transaction-pool/src/api.rs +++ b/client/transaction-pool/src/api.rs @@ -29,10 +29,10 @@ use sc_client_api::{ }; use sp_runtime::{ generic::BlockId, traits::{self, Block as BlockT, BlockIdTo, Header as HeaderT, Hash as HashT}, - transaction_validity::TransactionValidity, + transaction_validity::{TransactionValidity, TransactionSource}, }; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; -use sp_api::ProvideRuntimeApi; +use sp_api::{ProvideRuntimeApi, ApiExt}; use crate::error::{self, Error}; @@ -81,6 +81,7 @@ impl sc_transaction_graph::ChainApi for FullChainApi, + source: TransactionSource, uxt: sc_transaction_graph::ExtrinsicFor, ) -> Self::ValidationFuture { let (tx, rx) = oneshot::channel(); @@ -88,8 +89,19 @@ impl sc_transaction_graph::ChainApi for FullChainApi, _>( + &at, |v| v >= 2, + ) + .unwrap_or_default(); + let res = if has_v2 { + runtime_api.validate_transaction(&at, source, uxt) + } else { + #[allow(deprecated)] // old validate_transaction + runtime_api.validate_transaction_before_version_2(&at, uxt) + }; + let res = res.map_err(|e| Error::RuntimeApi(format!("{:?}", e))); if let Err(e) = tx.send(res) { log::warn!("Unable to send a validate transaction result: {:?}", e); } @@ -160,6 +172,7 @@ impl sc_transaction_graph::ChainApi for LightChainApi, + source: TransactionSource, uxt: sc_transaction_graph::ExtrinsicFor, ) -> Self::ValidationFuture { let header_hash = self.client.expect_block_hash_from_id(at); @@ -174,7 +187,7 @@ impl sc_transaction_graph::ChainApi for LightChainApi = Box>> + Send>; + +type ReadyIteratorFor = BoxedReadyIterator, sc_transaction_graph::ExtrinsicFor>; + +type PolledIterator = Pin> + Send>>; + /// Basic implementation of transaction pool that can be customized by providing PoolApi. pub struct BasicPool where @@ -54,6 +61,48 @@ pub struct BasicPool api: Arc, revalidation_strategy: Arc>>>, revalidation_queue: Arc>, + ready_poll: Arc, Block>>>, +} + +struct ReadyPoll { + updated_at: NumberFor, + pollers: Vec<(NumberFor, oneshot::Sender)>, +} + +impl Default for ReadyPoll { + fn default() -> Self { + Self { + updated_at: NumberFor::::zero(), + pollers: Default::default(), + } + } +} + +impl ReadyPoll { + fn trigger(&mut self, number: NumberFor, iterator_factory: impl Fn() -> T) { + self.updated_at = number; + + let mut idx = 0; + while idx < self.pollers.len() { + if self.pollers[idx].0 <= number { + let poller_sender = self.pollers.swap_remove(idx); + log::debug!(target: "txpool", "Sending ready signal at block {}", number); + let _ = poller_sender.1.send(iterator_factory()); + } else { + idx += 1; + } + } + } + + fn add(&mut self, number: NumberFor) -> oneshot::Receiver { + let (sender, receiver) = oneshot::channel(); + self.pollers.push((number, sender)); + receiver + } + + fn updated_at(&self) -> NumberFor { + self.updated_at + } } #[cfg(not(target_os = "unknown"))] @@ -102,6 +151,27 @@ impl BasicPool Self::with_revalidation_type(options, pool_api, RevalidationType::Full) } + /// Create new basic transaction pool with provided api, for tests. + #[cfg(test)] + pub fn new_test( + pool_api: Arc, + ) -> (Self, Pin + Send>>, intervalier::BackSignalControl) { + let pool = Arc::new(sc_transaction_graph::Pool::new(Default::default(), pool_api.clone())); + let (revalidation_queue, background_task, notifier) = + revalidation::RevalidationQueue::new_test(pool_api.clone(), pool.clone()); + ( + BasicPool { + api: pool_api, + pool, + revalidation_queue: Arc::new(revalidation_queue), + revalidation_strategy: Arc::new(Mutex::new(RevalidationStrategy::Always)), + ready_poll: Default::default(), + }, + background_task, + notifier, + ) + } + /// Create new basic transaction pool with provided api and custom /// revalidation type. pub fn with_revalidation_type( @@ -128,6 +198,7 @@ impl BasicPool RevalidationType::Full => RevalidationStrategy::Always, } )), + ready_poll: Default::default(), }, background_task, ) @@ -152,37 +223,40 @@ impl TransactionPool for BasicPool fn submit_at( &self, at: &BlockId, + source: TransactionSource, xts: Vec>, ) -> PoolFuture, Self::Error>>, Self::Error> { let pool = self.pool.clone(); let at = *at; async move { - pool.submit_at(&at, xts, false).await + pool.submit_at(&at, source, xts, false).await }.boxed() } fn submit_one( &self, at: &BlockId, + source: TransactionSource, xt: TransactionFor, ) -> PoolFuture, Self::Error> { let pool = self.pool.clone(); let at = *at; async move { - pool.submit_one(&at, xt).await + pool.submit_one(&at, source, xt).await }.boxed() } fn submit_and_watch( &self, at: &BlockId, + source: TransactionSource, xt: TransactionFor, ) -> PoolFuture>, Self::Error> { let at = *at; let pool = self.pool.clone(); async move { - pool.submit_and_watch(&at, xt) + pool.submit_and_watch(&at, source, xt) .map(|result| result.map(|watcher| Box::new(watcher.into_stream()) as _)) .await }.boxed() @@ -196,10 +270,6 @@ impl TransactionPool for BasicPool self.pool.validated_pool().status() } - fn ready(&self) -> Box>> { - Box::new(self.pool.validated_pool().ready()) - } - fn import_notification_stream(&self) -> ImportNotificationStream> { self.pool.validated_pool().import_notification_stream() } @@ -215,6 +285,27 @@ impl TransactionPool for BasicPool fn ready_transaction(&self, hash: &TxHash) -> Option> { self.pool.validated_pool().ready_by_hash(hash) } + + fn ready_at(&self, at: NumberFor) -> PolledIterator { + if self.ready_poll.lock().updated_at() >= at { + let iterator: ReadyIteratorFor = Box::new(self.pool.validated_pool().ready()); + return Box::pin(futures::future::ready(iterator)); + } + + Box::pin( + self.ready_poll + .lock() + .add(at) + .map(|received| received.unwrap_or_else(|e| { + log::warn!("Error receiving pending set: {:?}", e); + Box::new(vec![].into_iter()) + })) + ) + } + + fn ready(&self) -> ReadyIteratorFor { + Box::new(self.pool.validated_pool().ready()) + } } #[cfg_attr(test, derive(Debug))] @@ -316,7 +407,7 @@ impl MaintainedTransactionPool for BasicPool let block_number = match api.block_id_to_number(&id) { Ok(Some(number)) => number, _ => { - log::trace!(target: "txqueue", "Skipping chain event - no number for that block {:?}", id); + log::trace!(target: "txpool", "Skipping chain event - no number for that block {:?}", id); return Box::pin(ready(())); } }; @@ -329,6 +420,7 @@ impl MaintainedTransactionPool for BasicPool let revalidation_strategy = self.revalidation_strategy.clone(); let retracted = retracted.clone(); let revalidation_queue = self.revalidation_queue.clone(); + let ready_poll = self.ready_poll.clone(); async move { // We don't query block if we won't prune anything @@ -348,6 +440,10 @@ impl MaintainedTransactionPool for BasicPool } } + 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 next_action.resubmit { let mut resubmit_transactions = Vec::new(); @@ -366,7 +462,14 @@ impl MaintainedTransactionPool for BasicPool resubmit_transactions.extend(block_transactions); } - if let Err(e) = pool.submit_at(&id, resubmit_transactions, true).await { + if let Err(e) = pool.submit_at( + &id, + // These transactions are coming from retracted blocks, we should + // simply consider them external. + TransactionSource::External, + resubmit_transactions, + true + ).await { log::debug!( target: "txpool", "[{:?}] Error re-submitting transactions: {:?}", id, e diff --git a/client/transaction-pool/src/revalidation.rs b/client/transaction-pool/src/revalidation.rs index 4b3f9955ca7a9a954debcaea19f1bf19a3bdff9b..5a3b2521c362a9acfae683650bbbafbdfff10e6a 100644 --- a/client/transaction-pool/src/revalidation.rs +++ b/client/transaction-pool/src/revalidation.rs @@ -23,9 +23,8 @@ use sp_runtime::traits::{Zero, SaturatedConversion}; use sp_runtime::generic::BlockId; use sp_runtime::transaction_validity::TransactionValidityError; -use futures::{prelude::*, channel::mpsc, stream::unfold}; +use futures::{prelude::*, channel::mpsc}; use std::time::Duration; -use futures_timer::Delay; #[cfg(not(test))] const BACKGROUND_REVALIDATION_INTERVAL: Duration = Duration::from_millis(200); @@ -53,12 +52,6 @@ struct RevalidationWorker { impl Unpin for RevalidationWorker {} -fn interval(duration: Duration) -> impl Stream + Unpin { - unfold((), move |_| { - Delay::new(duration).map(|_| Some(((), ()))) - }).map(drop) -} - /// Revalidate batch of transaction. /// /// Each transaction is validated against chain, and invalid are @@ -78,7 +71,7 @@ async fn batch_revalidate( None => continue, }; - match api.validate_transaction(&BlockId::Number(at), ext.data.clone()).await { + match api.validate_transaction(&BlockId::Number(at), ext.source, ext.data.clone()).await { Ok(Err(TransactionValidityError::Invalid(err))) => { log::debug!(target: "txpool", "[{:?}]: Revalidation: invalid {:?}", ext_hash, err); invalid_hashes.push(ext_hash); @@ -94,6 +87,7 @@ async fn batch_revalidate( ValidatedTransaction::valid_at( at.saturated_into::(), ext_hash, + ext.source, ext.data.clone(), api.hash_and_length(&ext.data).1, validity, @@ -206,8 +200,13 @@ 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(mut self, from_queue: mpsc::UnboundedReceiver>) { - let interval = interval(BACKGROUND_REVALIDATION_INTERVAL).fuse(); + pub async fn run( + mut self, + from_queue: mpsc::UnboundedReceiver>, + interval: R, + ) where R: Send, R::Guard: Send + { + let interval = interval.into_stream().fuse(); let from_queue = from_queue.fuse(); futures::pin_mut!(interval, from_queue); let this = &mut self; @@ -269,9 +268,12 @@ where } } - /// New revalidation queue with background worker. - pub fn new_background(api: Arc, pool: Arc>) -> - (Self, Pin + Send>>) + pub fn new_with_interval( + api: Arc, + pool: Arc>, + interval: R, + ) -> (Self, Pin + Send>>) + where R: Send + 'static, R::Guard: Send { let (to_worker, from_queue) = mpsc::unbounded(); @@ -284,7 +286,25 @@ where background: Some(to_worker), }; - (queue, worker.run(from_queue).boxed()) + (queue, worker.run(from_queue, interval).boxed()) + } + + /// New revalidation queue with background worker. + pub fn new_background(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(test)] + 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) } /// Queue some transaction for later revalidation. @@ -312,9 +332,9 @@ where #[cfg(test)] mod tests { - use super::*; use sc_transaction_graph::Pool; + use sp_transaction_pool::TransactionSource; use substrate_test_runtime_transaction_pool::{TestApi, uxt}; use futures::executor::block_on; use substrate_test_runtime_client::{ @@ -334,7 +354,9 @@ mod tests { 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), uxt.clone())).expect("Should be valid"); + 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])); @@ -343,4 +365,4 @@ mod tests { // number of ready assert_eq!(pool.validated_pool().status().ready, 1); } -} \ No newline at end of file +} diff --git a/client/transaction-pool/src/testing/pool.rs b/client/transaction-pool/src/testing/pool.rs index 7494ba2684f1b5d5144f2c8bc4bbce1a7b3a93af..c6c54d97209445f17b2daad50377555cdd0906e4 100644 --- a/client/transaction-pool/src/testing/pool.rs +++ b/client/transaction-pool/src/testing/pool.rs @@ -20,25 +20,32 @@ use futures::executor::block_on; use txpool::{self, Pool}; use sp_runtime::{ generic::BlockId, - transaction_validity::ValidTransaction, + transaction_validity::{ValidTransaction, InvalidTransaction, TransactionSource}, }; use substrate_test_runtime_client::{ - runtime::{Block, Hash, Index, Header, Extrinsic}, + runtime::{Block, Hash, Index, Header, Extrinsic, Transfer}, AccountKeyring::*, }; use substrate_test_runtime_transaction_pool::{TestApi, uxt}; -use crate::revalidation::BACKGROUND_REVALIDATION_INTERVAL; +use futures::{prelude::*, task::Poll}; +use codec::Encode; fn pool() -> Pool { Pool::new(Default::default(), TestApi::with_alice_nonce(209).into()) } -fn maintained_pool() -> (BasicPool, futures::executor::ThreadPool) { - let (pool, background_task) = BasicPool::new(Default::default(), std::sync::Arc::new(TestApi::with_alice_nonce(209))); +fn maintained_pool() -> ( + BasicPool, + futures::executor::ThreadPool, + intervalier::BackSignalControl, +) { + let (pool, background_task, notifier) = BasicPool::new_test( + std::sync::Arc::new(TestApi::with_alice_nonce(209)) + ); let thread_pool = futures::executor::ThreadPool::new().unwrap(); - thread_pool.spawn_ok(background_task.expect("basic pool have background task")); - (pool, thread_pool) + thread_pool.spawn_ok(background_task); + (pool, thread_pool, notifier) } fn header(number: u64) -> Header { @@ -51,10 +58,12 @@ fn header(number: u64) -> Header { } } +const SOURCE: TransactionSource = TransactionSource::External; + #[test] fn submission_should_work() { let pool = pool(); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 209))).unwrap(); + block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).unwrap(); let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); assert_eq!(pending, vec![209]); @@ -63,8 +72,8 @@ fn submission_should_work() { #[test] fn multiple_submission_should_work() { let pool = pool(); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 209))).unwrap(); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 210))).unwrap(); + block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).unwrap(); + block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 210))).unwrap(); let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); assert_eq!(pending, vec![209, 210]); @@ -73,7 +82,7 @@ fn multiple_submission_should_work() { #[test] fn early_nonce_should_be_culled() { let pool = pool(); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 208))).unwrap(); + block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 208))).unwrap(); let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); assert_eq!(pending, Vec::::new()); @@ -83,11 +92,11 @@ fn early_nonce_should_be_culled() { fn late_nonce_should_be_queued() { let pool = pool(); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 210))).unwrap(); + block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 210))).unwrap(); let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); assert_eq!(pending, Vec::::new()); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 209))).unwrap(); + block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).unwrap(); let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); assert_eq!(pending, vec![209, 210]); } @@ -95,8 +104,8 @@ fn late_nonce_should_be_queued() { #[test] fn prune_tags_should_work() { let pool = pool(); - let hash209 = block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 209))).unwrap(); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 210))).unwrap(); + let hash209 = block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).unwrap(); + block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 210))).unwrap(); let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); assert_eq!(pending, vec![209, 210]); @@ -117,16 +126,16 @@ fn prune_tags_should_work() { fn should_ban_invalid_transactions() { let pool = pool(); let uxt = uxt(Alice, 209); - let hash = block_on(pool.submit_one(&BlockId::number(0), uxt.clone())).unwrap(); + let hash = block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt.clone())).unwrap(); pool.validated_pool().remove_invalid(&[hash]); - block_on(pool.submit_one(&BlockId::number(0), uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt.clone())).unwrap_err(); // when let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); assert_eq!(pending, Vec::::new()); // then - block_on(pool.submit_one(&BlockId::number(0), uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt.clone())).unwrap_err(); } #[test] @@ -137,7 +146,7 @@ fn should_correctly_prune_transactions_providing_more_than_one_tag() { })); let pool = Pool::new(Default::default(), api.clone()); let xt = uxt(Alice, 209); - block_on(pool.submit_one(&BlockId::number(0), xt.clone())).expect("1. Imported"); + block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.clone())).expect("1. Imported"); assert_eq!(pool.validated_pool().status().ready, 1); // remove the transaction that just got imported. @@ -150,7 +159,7 @@ fn should_correctly_prune_transactions_providing_more_than_one_tag() { // so now let's insert another transaction that also provides the 155 api.increment_nonce(Alice.into()); let xt = uxt(Alice, 211); - block_on(pool.submit_one(&BlockId::number(2), xt.clone())).expect("2. Imported"); + block_on(pool.submit_one(&BlockId::number(2), SOURCE, xt.clone())).expect("2. Imported"); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 1); let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); @@ -186,9 +195,9 @@ fn block_event_with_retracted(id: u64, retracted: Vec) -> ChainEvent>(), @@ -395,11 +403,11 @@ fn should_push_watchers_during_maintaince() { #[test] fn can_track_heap_size() { - let (pool, _guard) = maintained_pool(); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 209))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 210))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 211))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), uxt(Alice, 212))).expect("1. Imported"); + 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); } @@ -410,7 +418,9 @@ fn finalization() { let api = TestApi::with_alice_nonce(209); api.push_block(1, vec![]); let (pool, _background) = BasicPool::new(Default::default(), api.into()); - let watcher = block_on(pool.submit_and_watch(&BlockId::number(1), xt.clone())).expect("1. Imported"); + 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()]); let header = pool.api.chain().read().header_by_number.get(&2).cloned().unwrap(); @@ -460,7 +470,9 @@ fn fork_aware_finalization() { // block B1 { - let watcher = block_on(pool.submit_and_watch(&BlockId::number(1), from_alice.clone())).expect("1. Imported"); + let watcher = block_on( + pool.submit_and_watch(&BlockId::number(1), SOURCE, from_alice.clone()) + ).expect("1. Imported"); let header = pool.api.push_block(2, vec![from_alice.clone()]); canon_watchers.push((watcher, header.hash())); assert_eq!(pool.status().ready, 1); @@ -481,8 +493,9 @@ fn fork_aware_finalization() { // block C2 { let header = pool.api.push_fork_block_with_parent(b1, vec![from_dave.clone()]); - from_dave_watcher = block_on(pool.submit_and_watch(&BlockId::number(1), from_dave.clone())) - .expect("1. Imported"); + from_dave_watcher = block_on( + pool.submit_and_watch(&BlockId::number(1), SOURCE, from_dave.clone()) + ).expect("1. Imported"); assert_eq!(pool.status().ready, 1); let event = ChainEvent::NewBlock { id: BlockId::Hash(header.hash()), @@ -497,7 +510,9 @@ fn fork_aware_finalization() { // block D2 { - from_bob_watcher = block_on(pool.submit_and_watch(&BlockId::number(1), from_bob.clone())).expect("1. Imported"); + from_bob_watcher = block_on( + pool.submit_and_watch(&BlockId::number(1), SOURCE, from_bob.clone()) + ).expect("1. Imported"); assert_eq!(pool.status().ready, 1); let header = pool.api.push_fork_block_with_parent(c2, vec![from_bob.clone()]); @@ -514,7 +529,9 @@ fn fork_aware_finalization() { // block C1 { - let watcher = block_on(pool.submit_and_watch(&BlockId::number(1), from_charlie.clone())).expect("1.Imported"); + let watcher = 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()]); @@ -534,7 +551,9 @@ fn fork_aware_finalization() { // block D1 { let xt = uxt(Eve, 0); - let w = block_on(pool.submit_and_watch(&BlockId::number(1), xt.clone())).expect("1. Imported"); + 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()]); canon_watchers.push((w, header.hash())); @@ -600,5 +619,89 @@ fn fork_aware_finalization() { assert_eq!(stream.next(), Some(TransactionStatus::Finalized(e1.clone()))); assert_eq!(stream.next(), None); } +} + +#[test] +fn ready_set_should_not_resolve_before_block_update() { + let (pool, _guard, _notifier) = maintained_pool(); + let xt1 = uxt(Alice, 209); + block_on(pool.submit_one(&BlockId::number(1), SOURCE, xt1.clone())).expect("1. Imported"); + + assert!(pool.ready_at(1).now_or_never().is_none()); +} + +#[test] +fn ready_set_should_resolve_after_block_update() { + let (pool, _guard, _notifier) = maintained_pool(); + pool.api.push_block(1, vec![]); + + let xt1 = uxt(Alice, 209); + block_on(pool.submit_one(&BlockId::number(1), SOURCE, xt1.clone())).expect("1. Imported"); + block_on(pool.maintain(block_event(1))); + + assert!(pool.ready_at(1).now_or_never().is_some()); +} + +#[test] +fn ready_set_should_eventually_resolve_when_block_update_arrives() { + let (pool, _guard, _notifier) = maintained_pool(); + pool.api.push_block(1, vec![]); + + let xt1 = uxt(Alice, 209); + + block_on(pool.submit_one(&BlockId::number(1), SOURCE, xt1.clone())).expect("1. Imported"); + + let noop_waker = futures::task::noop_waker(); + 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) { + panic!("Ready set should not be ready before block update!"); + } + + block_on(pool.maintain(block_event(1))); + + match ready_set_future.poll_unpin(&mut context) { + Poll::Pending => { + panic!("Ready set should become ready after block update!"); + }, + Poll::Ready(iterator) => { + let data = iterator.collect::>(); + assert_eq!(data.len(), 1); + } + } +} + +#[test] +fn should_not_accept_old_signatures() { + use std::convert::TryFrom; + + let client = Arc::new(substrate_test_runtime_client::new()); + let pool = Arc::new( + BasicPool::new(Default::default(), Arc::new(FullChainApi::new(client))).0 + ); + + let transfer = Transfer { + from: Alice.into(), + to: Bob.into(), + nonce: 0, + amount: 1, + }; + let _bytes: sp_core::sr25519::Signature = transfer.using_encoded(|e| Alice.sign(e)).into(); + + // generated with schnorrkel 0.1.1 from `_bytes` + let old_singature = sp_core::sr25519::Signature::try_from(&hex::decode( + "c427eb672e8c441c86d31f1a81b22b43102058e9ce237cabe9897ea5099ffd426cd1c6a1f4f2869c3df57901d36bedcb295657adb3a4355add86ed234eb83108" + ).expect("hex invalid")[..]).expect("signature construction failed"); + + let xt = Extrinsic::Transfer { transfer, signature: old_singature, exhaust_resources_when_not_first: false }; + + assert_matches::assert_matches!( + block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.clone())), + Err(error::Error::Pool( + sp_transaction_pool::error::Error::InvalidTransaction(InvalidTransaction::BadProof) + )), + "Should be invalid transactiono with bad proof", + ); } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..6ac08309348f0140a3e59adb48d2a3146845f465 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,54 @@ +# Changelog + +The format is based on [Keep a Changelog]. + +[Keep a Changelog]: http://keepachangelog.com/en/1.0.0/ + +## Unreleased + +## 2.0.0-alpha.4 -> 2.0.0-alpha.5 + +Runtime +------- + +* pallet-evm: configurable gasometer config (#5320) +* Adds new event phase `Initialization` (#5302) + +## 2.0.0-alpha.3 -> 2.0.0-alpha.4 + +Runtime +------- + +* Move runtime upgrade to `frame-executive` (#5197) +* Split fees and tips between author and treasury independently (#5207) +* Refactor session away from needless double_maps (#5202) +* Remove `secp256k1` from WASM build (#5187) +* Introduce default-setting prime for collective (#5137) +* Adds `vested_transfer` to Vesting pallet (#5029) +* Change extrinsic_count to extrinsic_index in pallet-utility (#5044) + +Client +------ + +* client/finality-grandpa: Add Prometheus metrics to GossipValidator (#5237) +* removes use of sc_client::Client from node-transaction-factory (#5158) +* removes use of sc_client::Client from sc_network (#5147) +* Use CLI to configure max instances cache (#5177) +* client/service/src/builder.rs: Add build_info metric (#5192) +* Remove substrate-ui.parity.io from CORS whitelist (#5142) +* removes use of sc_client::Client from sc-rpc (#5063) +* Use 128mb for db cache default (#5134) +* Drop db-cache default from 1gig to 32mb (#5128) +* Add more metrics to prometheus (#5034) + +API +--- + +* Produce block always on updated transaction pool state (#5227) +* Add `ext_terminate` (#5234) +* Add ext_transfer call (#5169) +* ChainSpec trait (#5185) +* client/authority-discovery: Instrument code with Prometheus (#5195) +* Don't include `:code` by default in storage proofs (#5179) +* client/network-gossip: Merge GossipEngine and GossipEngineInner (#5042) +* Introduce `on_runtime_upgrade` (#5058) \ No newline at end of file diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 701df299a35453ce71a037150d46d329847d5f80..d4404346294a53abc2520cdfab7f7a4fc582f1f0 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -20,15 +20,22 @@ # Wasm execution and the wasm side of Substrate Runtime Interface /client/executor/ @pepyakin -/primitives/io/ @pepyakin +/primitives/io/ @pepyakin @NikVolf + +# Crypto, execution extensions, etc. +/primitives/core/ @NikVolf + +# Block production +/primitives/authorship/ @NikVolf +/client/basic-authorship/ @NikVolf # Sandboxing capability of Substrate Runtime /primitives/sr-sandbox/ @pepyakin /primitives/core/src/sandbox.rs @pepyakin # Transaction pool -/client/transaction-pool/ @tomusdrw -/primitives/transaction-pool/ @tomusdrw +/client/transaction-pool/ @tomusdrw @NikVolf +/primitives/transaction-pool/ @tomusdrw @NikVolf # Offchain /client/offchain/ @tomusdrw @@ -59,9 +66,14 @@ # Inflation points /frame/staking/src/inflation.rs @thiolliere -# NPoS and Governance +# NPoS and Governance and Phragmén /frame/staking/ @kianenigma /frame/elections/ @kianenigma +/frame/elections-phragmen/ @kianenigma +/primitives/phragmen/ @kianenigma + +# Fixed point arithmetic +/primitives/sp-arithmetic/ @kianenigma @thiolliere # End to end testing of substrate node /bin/node/executor/ @kianenigma diff --git a/docs/CONTRIBUTING.adoc b/docs/CONTRIBUTING.adoc index cdd9809fff864cdb1b26321d76567ac0152233e5..5a1a4466b0a67410a1257e76de20ce4c7adc25f1 100644 --- a/docs/CONTRIBUTING.adoc +++ b/docs/CONTRIBUTING.adoc @@ -25,9 +25,10 @@ Merging pull requests once CI is successful: - it does not alter any logic (e.g. comments, dependencies, docs), then it may be tagged https://github.com/paritytech/substrate/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+label%3AA2-insubstantial[`insubstantial`] and merged by its author once CI is complete. - it is an urgent fix with no large change to logic, then it may be merged after a non-author contributor has approved the review once CI is complete. -. Once a PR is ready for review please add the https://github.com/paritytech/substrate/pulls?q=is%3Apr+is%3Aopen+label%3AA0-pleasereview[`pleasereview`] label. Generally PRs should sit with this label for 48 hours in order to garner feedback. It may be merged before if all relevant parties had a look at it. -. If the first review is not an approval, swap `A0-pleasereview` to any label `[A3, A7]` to indicate that the PR has received some feedback, but needs further work. For example. https://github.com/paritytech/substrate/labels/A3-inprogress[`A3-inprogress`] is a general indicator that the PR is work in progress and https://github.com/paritytech/substrate/labels/A4-gotissues[`A4-gotissues`] means that it has significant problems that need fixing. Once the work is done, change the label back to `A0-pleasereview`. You might end up swapping a few times back and forth to climb up the A label group. Once a PR is https://github.com/paritytech/substrate/labels/A8-looksgood[`A8-looksgood`], it is ready to merge. -. PRs that break the external API must be tagged with https://github.com/paritytech/substrate/labels/B2-breaksapi[`breaksapi`], when it changes the FRAME or consensus of running system with https://github.com/paritytech/substrate/labels/B3-breaksconsensus[`breaksconsensus`] +. Once a PR is ready for review please add the https://github.com/paritytech/substrate/pulls?q=is%3Apr+is%3Aopen+label%3AA0-pleasereview[`A0-pleasereview`] label. Generally PRs should sit with this label for 48 hours in order to garner feedback. It may be merged before if all relevant parties had a look at it. +. If the first review is not an approval, swap `A0-pleasereview` to any label `[A3, A7]` to indicate that the PR has received some feedback, but needs further work. For example. https://github.com/paritytech/substrate/labels/A3-inprogress[`A3-inprogress`] is a general indicator that the PR is work in progress and https://github.com/paritytech/substrate/labels/A4-gotissues[`A4-gotissues`] means that it has significant problems that need fixing. Once the work is done, change the label back to `A0-pleasereview`. You might end up swapping a few times back and forth to climb up the A label group. Once a PR is https://github.com/paritytech/substrate/labels/A8-mergeoncegreen[`A8-mergeoncegreen`], it is ready to merge. +. PRs must be tagged with respect to _release notes_ with https://github.com/paritytech/substrate/labels/B0-silent[`B0-silent`] and `B1-..`. The former indicates that no changes should be mentioned in any release notes. The latter indicates that the changes should be reported in the corresponding release note +. PRs that break the external API must be tagged with https://github.com/paritytech/substrate/labels/B2-breaksapi[`B2-breaksapi`], when it changes the FRAME or consensus of running system with https://github.com/paritytech/substrate/labels/B3-breaksconsensus[`B3-breaksconsensus`] . No PR should be merged until all reviews' comments are addressed. *Reviewing pull requests*: @@ -48,16 +49,19 @@ When reviewing a pull request, the end-goal is to suggest useful changes to the === Updating Polkadot as well -If your PR changes the external APIs or interfaces used by Polkadot, **a corresponding PR on Polkadot must** be submitted as well. If you tagged the PR with `breaksapi` or `breaksconsensus` this is most certainly the case, in all other cases check for it by running step 1. +**All pull requests will be checked agains either Polkadot master, or your provided Polkadot companion PR**. That is, If your PR changes the external APIs or interfaces used by Polkadot. If you tagged the PR with `breaksapi` or `breaksconsensus` this is most certainly the case, in all other cases check for it by running step 1 below. -To update a corresponding Polkadot PR: +To create a Polkadot companion PR: -0. Pull latest Polkadot master (or clone it, if you haven't yet). -1. Replace `polkadot-master` in all `Cargo.toml` with the name of the PR-branch - e.g. by running `find . -name "Cargo.toml" -exec sed -i "s/polkadot-master/PR_BRANCH/g" {}` (and to your repo, if the branch is not on mainline); Commit this change. -2. Make the changes required to pass the build again. -3. Submit all this as a PR against the Polkadot Repo, link that new PR in the existing substrate PR for reference -4. Wait for reviews on both -5. Once both PRs have been green lit, merge the Substrate PR into master and sync the changes onto the `polkadot-master` on mainline (push it). Now revert that first commit in your Polkadot PR, reverting the name back to `polkadot-master`, push and wait for the CI to confirm all is fine. Then merge the Polkadot PR. +. Pull latest Polkadot master (or clone it, if you haven't yet). +. Override your local cargo config to point to your local substrate (pointing to your WIP branch): place `paths = ["path/to/substrate"]` in `~/.cargo/config`. +. 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]". +. Now you should see that the `check_polkadot` CI job will build your Substrate PR agains the mentioned Polkadot branch in your PR description. +. Wait for reviews on both +. Once both PRs have been green lit, they can both be merged 🍻. + +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. As there might be multiple pending PRs that might conflict with one another, a) you should not merge the substrate PR until the Polkadot PR has also been reviewed and b) both should be merged pretty quickly after another to not block others. diff --git a/frame/assets/Cargo.toml b/frame/assets/Cargo.toml index 5b0b5f096a91fdfea93956119879c8873d714de9..8b242ff0e8ef149e47b86365ba14acf7abe9cc0a 100644 --- a/frame/assets/Cargo.toml +++ b/frame/assets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-assets" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,18 +10,18 @@ description = "FRAME asset management pallet" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } # Needed for various traits. In our case, `OnFinalize`. -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } # Needed for type-safe access to storage DB. -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } # `system` module provides us with all sorts of useful stuff and macros depend on it being around. -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", path = "../../primitives/io" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", path = "../../primitives/io" } [features] default = ["std"] @@ -32,3 +32,6 @@ std = [ "frame-support/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index af74f90461ae400cb6438b30816280f9ec2555ad..388eb7780bd6dc7df7525d9e015c20790b0c4594 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -157,6 +157,7 @@ decl_module! { /// Issue a new class of fungible assets. There are, and will only ever be, `total` /// such assets and they'll all belong to the `origin` initially. It will have an /// identifier `AssetId` instance: this will be specified in the `Issued` event. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn issue(origin, #[compact] total: T::Balance) { let origin = ensure_signed(origin)?; @@ -170,6 +171,7 @@ decl_module! { } /// Move some assets from one holder to another. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn transfer(origin, #[compact] id: T::AssetId, target: ::Source, @@ -188,6 +190,7 @@ decl_module! { } /// Destroy any assets of `id` owned by `origin`. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn destroy(origin, #[compact] id: T::AssetId) { let origin = ensure_signed(origin)?; let balance = >::take((id, &origin)); @@ -294,7 +297,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } impl Trait for Test { diff --git a/frame/aura/Cargo.toml b/frame/aura/Cargo.toml index 77abfe4ae1f48aa062c50d4d516e62dd4042631d..36e79116618e35c296590c96a1666e03bd349784 100644 --- a/frame/aura/Cargo.toml +++ b/frame/aura/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-aura" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,20 +9,20 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME AURA consensus pallet" [dependencies] -sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/application-crypto" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/inherents" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } +sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/application-crypto" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } serde = { version = "1.0.101", optional = true } -pallet-session = { version = "2.0.0-alpha.2", default-features = false, path = "../session" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.2"} -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -sp-consensus-aura = { path = "../../primitives/consensus/aura", default-features = false, version = "0.8.0-alpha.2"} -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -sp-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/timestamp" } -pallet-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../timestamp" } +pallet-session = { version = "2.0.0-alpha.5", default-features = false, path = "../session" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.5"} +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +sp-consensus-aura = { path = "../../primitives/consensus/aura", default-features = false, version = "0.8.0-alpha.5"} +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +sp-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/timestamp" } +pallet-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../timestamp" } [dev-dependencies] @@ -46,3 +46,6 @@ std = [ "sp-timestamp/std", "pallet-timestamp/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/aura/src/mock.rs b/frame/aura/src/mock.rs index b191e1d05ddd48865f0b9b2871c6077224f8821f..05a161ee49c3d6c9ac7c51028d31f1dd9d193bc8 100644 --- a/frame/aura/src/mock.rs +++ b/frame/aura/src/mock.rs @@ -62,7 +62,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/authority-discovery/Cargo.toml b/frame/authority-discovery/Cargo.toml index c82d1eab588b39fcf27f5924ca5471a8b4b616fd..f67d4ee038ab374a5cd2c75c947f141fc58fdde2 100644 --- a/frame/authority-discovery/Cargo.toml +++ b/frame/authority-discovery/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-authority-discovery" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,20 +9,20 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for authority discovery" [dependencies] -sp-authority-discovery = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/authority-discovery" } -sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/application-crypto" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } +sp-authority-discovery = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/authority-discovery" } +sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/application-crypto" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } serde = { version = "1.0.101", optional = true } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -pallet-session = { version = "2.0.0-alpha.2", features = ["historical" ], path = "../session", default-features = false } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +pallet-session = { version = "2.0.0-alpha.5", features = ["historical" ], path = "../session", default-features = false } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -sp-staking = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/staking" } +sp-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/staking" } [features] default = ["std"] @@ -39,3 +39,6 @@ std = [ "frame-support/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/authority-discovery/src/lib.rs b/frame/authority-discovery/src/lib.rs index 0479e710c57fd0bd42650c7b4e3601eccbd4b7a3..b8f28b432ba75f14594ee50e9b6fbfeb0ee73312 100644 --- a/frame/authority-discovery/src/lib.rs +++ b/frame/authority-discovery/src/lib.rs @@ -121,6 +121,7 @@ mod tests { type ValidatorId = AuthorityId; type ValidatorIdOf = ConvertInto; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = pallet_session::PeriodicSessions; } impl pallet_session::historical::Trait for Test { @@ -158,7 +159,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/authorship/Cargo.toml b/frame/authorship/Cargo.toml index 2a01cdbd8ad85b22245bf68b8505b4c8be846d6c..fb966113d55ee67fda4dcf946487f82a9185d6a6 100644 --- a/frame/authorship/Cargo.toml +++ b/frame/authorship/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-authorship" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" description = "Block and Uncle Author tracking for the FRAME" authors = ["Parity Technologies "] edition = "2018" @@ -9,15 +9,15 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/inherents" } -sp-authorship = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/authorship" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.2"} +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" } +sp-authorship = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/authorship" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.5"} impl-trait-for-tuples = "0.1.3" [features] @@ -33,3 +33,6 @@ std = [ "sp-io/std", "sp-authorship/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/authorship/src/lib.rs b/frame/authorship/src/lib.rs index 1869d408ca8d0560654b33e7d51317bb77e185bd..d71a71e5bf0ef18d1a8e35167200c0753f69d4df 100644 --- a/frame/authorship/src/lib.rs +++ b/frame/authorship/src/lib.rs @@ -27,7 +27,7 @@ use frame_support::traits::{FindAuthor, VerifySeal, Get}; use codec::{Encode, Decode}; use frame_system::ensure_none; use sp_runtime::traits::{Header as HeaderT, One, Zero}; -use frame_support::weights::SimpleDispatchInfo; +use frame_support::weights::{Weight, SimpleDispatchInfo, WeighData}; use sp_inherents::{InherentIdentifier, ProvideInherent, InherentData}; use sp_authorship::{INHERENT_IDENTIFIER, UnclesInherentData, InherentError}; @@ -185,7 +185,7 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin { type Error = Error; - fn on_initialize(now: T::BlockNumber) { + fn on_initialize(now: T::BlockNumber) -> Weight { let uncle_generations = T::UncleGenerations::get(); // prune uncles that are older than the allowed number of generations. if uncle_generations <= now { @@ -196,6 +196,8 @@ decl_module! { ::DidSetUncles::put(false); T::EventHandler::note_author(Self::author()); + + SimpleDispatchInfo::default().weigh_data(()) } fn on_finalize() { @@ -432,7 +434,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index 7e39e5bed5c29a607ef3faf077ab1964d3921c57..8f885500224053f5ccb8ecb80615b468a34bc514 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-babe" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,27 +9,23 @@ repository = "https://github.com/paritytech/substrate/" description = "Consensus extension module for BABE consensus. Collects on-chain randomness from VRF outputs and manages epoch transitions." [dependencies] -hex-literal = "0.2.1" -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } serde = { version = "1.0.101", optional = true } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/inherents" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/staking" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -pallet-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../timestamp" } -sp-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/timestamp" } -pallet-session = { version = "2.0.0-alpha.2", default-features = false, path = "../session" } -sp-consensus-babe = { version = "0.8.0-alpha.2", default-features = false, path = "../../primitives/consensus/babe" } -sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.2"} +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/staking" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +pallet-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../timestamp" } +sp-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/timestamp" } +pallet-session = { version = "2.0.0-alpha.5", default-features = false, path = "../session" } +sp-consensus-babe = { version = "0.8.0-alpha.5", default-features = false, path = "../../primitives/consensus/babe" } +sp-consensus-vrf = { version = "0.8.0-alpha.5", default-features = false, path = "../../primitives/consensus/vrf" } +sp-io = { path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.5"} [dev-dependencies] -lazy_static = "1.4.0" -parking_lot = "0.10.0" -sp-version = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/version" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -substrate-test-runtime = { version = "2.0.0-dev", path = "../../test-utils/runtime" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } [features] default = ["std"] @@ -45,6 +41,10 @@ std = [ "sp-timestamp/std", "sp-inherents/std", "sp-consensus-babe/std", + "sp-consensus-vrf/std", "pallet-session/std", "sp-io/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index 8013e0cd4548ddc5fae7ae7c1dc7c63b482f0cd7..32d5d3c7a5bdda1a49976d05c4e17b9f15fb33c3 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -19,14 +19,17 @@ #![cfg_attr(not(feature = "std"), no_std)] #![forbid(unused_must_use, unsafe_code, unused_variables, unused_must_use)] -#![deny(unused_imports)] -pub use pallet_timestamp; + +use pallet_timestamp; use sp_std::{result, prelude::*}; -use frame_support::{decl_storage, decl_module, traits::{FindAuthor, Get, Randomness as RandomnessT}}; +use frame_support::{ + decl_storage, decl_module, traits::{FindAuthor, Get, Randomness as RandomnessT}, + weights::{Weight, SimpleDispatchInfo, WeighData}, +}; use sp_timestamp::OnTimestampSet; -use sp_runtime::{generic::DigestItem, ConsensusEngineId, Perbill, PerThing}; -use sp_runtime::traits::{IsMember, SaturatedConversion, Saturating, Hash}; +use sp_runtime::{generic::DigestItem, ConsensusEngineId, Perbill}; +use sp_runtime::traits::{IsMember, SaturatedConversion, Saturating, Hash, One}; use sp_staking::{ SessionIndex, offence::{Offence, Kind}, @@ -39,7 +42,8 @@ use sp_consensus_babe::{ inherents::{INHERENT_IDENTIFIER, BabeInherentData}, digests::{NextEpochDescriptor, RawPreDigest}, }; -pub use sp_consensus_babe::{AuthorityId, VRF_OUTPUT_LENGTH, PUBLIC_KEY_LENGTH}; +use sp_consensus_vrf::schnorrkel; +pub use sp_consensus_babe::{AuthorityId, VRF_OUTPUT_LENGTH, RANDOMNESS_LENGTH, PUBLIC_KEY_LENGTH}; #[cfg(all(feature = "std", test))] mod tests; @@ -96,12 +100,9 @@ impl EpochChangeTrigger for SameAuthoritiesForever { } } -/// The length of the BABE randomness -pub const RANDOMNESS_LENGTH: usize = 32; - const UNDER_CONSTRUCTION_SEGMENT_LENGTH: usize = 256; -type MaybeVrf = Option<[u8; 32 /* VRF_OUTPUT_LENGTH */]>; +type MaybeVrf = Option; decl_storage! { trait Store for Module as Babe { @@ -131,10 +132,10 @@ decl_storage! { // NOTE: the following fields don't use the constants to define the // array size because the metadata API currently doesn't resolve the // variable to its underlying value. - pub Randomness get(fn randomness): [u8; 32 /* RANDOMNESS_LENGTH */]; + pub Randomness get(fn randomness): schnorrkel::Randomness; /// Next epoch randomness. - NextRandomness: [u8; 32 /* RANDOMNESS_LENGTH */]; + NextRandomness: schnorrkel::Randomness; /// Randomness under construction. /// @@ -146,7 +147,7 @@ decl_storage! { /// We reset all segments and return to `0` at the beginning of every /// epoch. SegmentIndex build(|_| 0): u32; - UnderConstruction: map hasher(twox_64_concat) u32 => Vec<[u8; 32 /* VRF_OUTPUT_LENGTH */]>; + UnderConstruction: map hasher(twox_64_concat) u32 => Vec; /// Temporary value (cleared at block finalization) which is `Some` /// if per-block initialization has already been called for current block. @@ -173,8 +174,10 @@ decl_module! { const ExpectedBlockTime: T::Moment = T::ExpectedBlockTime::get(); /// Initialization - fn on_initialize(now: T::BlockNumber) { + fn on_initialize(now: T::BlockNumber) -> Weight { Self::do_initialize(now); + + SimpleDispatchInfo::default().weigh_data(()) } /// Block finalization @@ -188,12 +191,6 @@ decl_module! { Self::deposit_vrf_output(&vrf_output); } } - - fn on_runtime_upgrade() { - for i in 0..=SegmentIndex::get() { - UnderConstruction::migrate_key_from_blake(i); - } - } } } @@ -216,13 +213,8 @@ impl FindAuthor for Module { { for (id, mut data) in digests.into_iter() { if id == BABE_ENGINE_ID { - let pre_digest = RawPreDigest::decode(&mut data).ok()?; - return Some(match pre_digest { - RawPreDigest::Primary { authority_index, .. } => - authority_index, - RawPreDigest::Secondary { authority_index, .. } => - authority_index, - }); + let pre_digest: RawPreDigest = RawPreDigest::decode(&mut data).ok()?; + return Some(pre_digest.authority_index()) } } @@ -315,12 +307,34 @@ impl Module { // epoch 0 as having started at the slot of block 1. We want to use // the same randomness and validator set as signalled in the genesis, // so we don't rotate the epoch. - now != sp_runtime::traits::One::one() && { + now != One::one() && { let diff = CurrentSlot::get().saturating_sub(Self::current_epoch_start()); diff >= T::EpochDuration::get() } } + /// Return the _best guess_ block number, at which the next epoch change is predicted to happen. + /// + /// Returns None if the prediction is in the past; This implies an error internally in the Babe + /// and should not happen under normal circumstances. + /// + /// In other word, this is only accurate if no slots are missed. Given missed slots, the slot + /// number will grow while the block number will not. Hence, the result can be interpreted as an + /// upper bound. + // -------------- IMPORTANT NOTE -------------- + // This implementation is linked to how [`should_epoch_change`] is working. This might need to + // be updated accordingly, if the underlying mechanics of slot and epochs change. + pub fn next_expected_epoch_change(now: T::BlockNumber) -> Option { + let next_slot = Self::current_epoch_start().saturating_add(T::EpochDuration::get()); + next_slot + .checked_sub(CurrentSlot::get()) + .map(|slots_remaining| { + // This is a best effort guess. Drifts in the slot/block ratio will cause errors here. + let blocks_remaining: T::BlockNumber = slots_remaining.saturated_into(); + now.saturating_add(blocks_remaining) + }) + } + /// DANGEROUS: Enact an epoch change. Should be done on every block where `should_epoch_change` has returned `true`, /// and the caller is the only caller of this function. /// @@ -332,10 +346,7 @@ impl Module { ) { // PRECONDITION: caller has done initialization and is guaranteed // by the session module to be called before this. - #[cfg(debug_assertions)] - { - assert!(Self::initialized().is_some()) - } + debug_assert!(Self::initialized().is_some()); // Update epoch index let epoch_index = EpochIndex::get() @@ -379,7 +390,7 @@ impl Module { >::deposit_log(log.into()) } - fn deposit_vrf_output(vrf_output: &[u8; VRF_OUTPUT_LENGTH]) { + fn deposit_vrf_output(vrf_output: &schnorrkel::RawVRFOutput) { let segment_idx = ::get(); let mut segment = ::get(&segment_idx); if segment.len() < UNDER_CONSTRUCTION_SEGMENT_LENGTH { @@ -389,7 +400,7 @@ impl Module { } else { // move onto the next segment and update the index. let segment_idx = segment_idx + 1; - ::insert(&segment_idx, &vec![*vrf_output]); + ::insert(&segment_idx, &vec![vrf_output.clone()]); ::put(&segment_idx); } } @@ -402,7 +413,7 @@ impl Module { return; } - let maybe_pre_digest = >::digest() + let maybe_pre_digest: Option = >::digest() .logs .iter() .filter_map(|s| s.as_pre_runtime()) @@ -434,11 +445,11 @@ impl Module { CurrentSlot::put(digest.slot_number()); - if let RawPreDigest::Primary { vrf_output, .. } = digest { + if let RawPreDigest::Primary(primary) = digest { // place the VRF output into 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. - Some(vrf_output) + Some(primary.vrf_output) } else { None } @@ -452,7 +463,7 @@ impl Module { /// Call this function exactly once when an epoch changes, to update the /// randomness. Returns the new randomness. - fn randomness_change_epoch(next_epoch_index: u64) -> [u8; RANDOMNESS_LENGTH] { + fn randomness_change_epoch(next_epoch_index: u64) -> schnorrkel::Randomness { let this_randomness = NextRandomness::get(); let segment_idx: u32 = ::mutate(|s| sp_std::mem::replace(s, 0)); @@ -481,6 +492,12 @@ impl OnTimestampSet for Module { fn on_timestamp_set(_moment: T::Moment) { } } +impl frame_support::traits::EstimateNextSessionRotation for Module { + fn estimate_next_session_rotation(now: T::BlockNumber) -> Option { + Self::next_expected_epoch_change(now) + } +} + impl sp_runtime::BoundToRuntimeAppPublic for Module { type Public = AuthorityId; } @@ -519,11 +536,11 @@ impl pallet_session::OneSessionHandler for Module { // // an optional size hint as to how many VRF outputs there were may be provided. fn compute_randomness( - last_epoch_randomness: [u8; RANDOMNESS_LENGTH], + last_epoch_randomness: schnorrkel::Randomness, epoch_index: u64, - rho: impl Iterator, + rho: impl Iterator, rho_size_hint: Option, -) -> [u8; RANDOMNESS_LENGTH] { +) -> schnorrkel::Randomness { let mut s = Vec::with_capacity(40 + rho_size_hint.unwrap_or(0) * VRF_OUTPUT_LENGTH); s.extend_from_slice(&last_epoch_randomness); s.extend_from_slice(&epoch_index.to_le_bytes()); diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 63943593b897a069b73813226f98e8ee9550f7f1..ea802b268e399c6f71577507df49b60288e880f5 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -16,14 +16,22 @@ //! Test utilities -use super::{Trait, Module, GenesisConfig}; +use codec::Encode; +use super::{Trait, Module, GenesisConfig, CurrentSlot}; use sp_runtime::{ - traits::IdentityLookup, Perbill, testing::{Header, UintAuthorityId}, impl_opaque_keys, + Perbill, impl_opaque_keys, + testing::{Header, UintAuthorityId, Digest, DigestItem}, + traits::IdentityLookup, +}; +use frame_system::InitKind; +use frame_support::{ + impl_outer_origin, parameter_types, StorageValue, + traits::OnInitialize, + weights::Weight, }; -use sp_version::RuntimeVersion; -use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; use sp_io; use sp_core::H256; +use sp_consensus_vrf::schnorrkel::{RawVRFOutput, RawVRFProof}; impl_outer_origin!{ pub enum Origin for Test where system = frame_system {} @@ -43,7 +51,6 @@ parameter_types! { pub const MinimumPeriod: u64 = 1; pub const EpochDuration: u64 = 3; pub const ExpectedBlockTime: u64 = 1; - pub const Version: RuntimeVersion = substrate_test_runtime::VERSION; pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(16); } @@ -53,7 +60,7 @@ impl frame_system::Trait for Test { type BlockNumber = u64; type Call = (); type Hash = H256; - type Version = Version; + type Version = (); type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = DummyValidatorId; type Lookup = IdentityLookup; @@ -65,7 +72,7 @@ impl frame_system::Trait for Test { type MaximumBlockLength = MaximumBlockLength; type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } @@ -79,11 +86,12 @@ impl pallet_session::Trait for Test { type Event = (); type ValidatorId = ::AccountId; type ShouldEndSession = Babe; - type SessionHandler = (Babe,Babe,); + type SessionHandler = (Babe,); type SessionManager = (); type ValidatorIdOf = (); type Keys = MockSessionKeys; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = Babe; } impl pallet_timestamp::Trait for Test { @@ -106,5 +114,44 @@ pub fn new_test_ext(authorities: Vec) -> sp_io::TestExternalit t.into() } +pub fn go_to_block(n: u64, s: u64) { + let pre_digest = make_pre_digest(0, s, RawVRFOutput([1; 32]), RawVRFProof([0xff; 64])); + System::initialize(&n, &Default::default(), &Default::default(), &pre_digest, InitKind::Full); + System::set_block_number(n); + if s > 1 { + CurrentSlot::put(s); + } + // includes a call into `Babe::do_initialize`. + Session::on_initialize(n); +} + +/// Slots will grow accordingly to blocks +pub fn progress_to_block(n: u64) { + let mut slot = Babe::current_slot() + 1; + for i in System::block_number()+1..=n { + go_to_block(i, slot); + slot += 1; + } +} + +pub fn make_pre_digest( + authority_index: sp_consensus_babe::AuthorityIndex, + slot_number: sp_consensus_babe::SlotNumber, + vrf_output: RawVRFOutput, + vrf_proof: RawVRFProof, +) -> Digest { + let digest_data = sp_consensus_babe::digests::RawPreDigest::Primary( + sp_consensus_babe::digests::RawPrimaryPreDigest { + authority_index, + slot_number, + vrf_output, + vrf_proof, + } + ); + let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode()); + Digest { logs: vec![log] } +} + pub type System = frame_system::Module; pub type Babe = Module; +pub type Session = pallet_session::Module; diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index 84f8166b10431a9d107cf82d831d4aaa3230dfe1..24aba100178e058112600873602218581613c3b3 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -17,9 +17,10 @@ //! Consensus extension module tests for BABE consensus. use super::*; -use mock::{new_test_ext, Babe, System}; -use sp_runtime::{traits::OnFinalize, testing::{Digest, DigestItem}}; +use mock::*; +use frame_support::traits::OnFinalize; use pallet_session::ShouldEndSession; +use sp_consensus_vrf::schnorrkel::{RawVRFOutput, RawVRFProof}; const EMPTY_RANDOMNESS: [u8; 32] = [ 74, 25, 49, 128, 53, 97, 244, 49, @@ -28,22 +29,6 @@ const EMPTY_RANDOMNESS: [u8; 32] = [ 217, 153, 138, 37, 48, 192, 248, 0, ]; -fn make_pre_digest( - authority_index: sp_consensus_babe::AuthorityIndex, - slot_number: sp_consensus_babe::SlotNumber, - vrf_output: [u8; sp_consensus_babe::VRF_OUTPUT_LENGTH], - vrf_proof: [u8; sp_consensus_babe::VRF_PROOF_LENGTH], -) -> Digest { - let digest_data = sp_consensus_babe::digests::RawPreDigest::Primary { - authority_index, - slot_number, - vrf_output, - vrf_proof, - }; - let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode()); - Digest { logs: vec![log] } -} - #[test] fn empty_randomness_is_correct() { let s = compute_randomness([0; RANDOMNESS_LENGTH], 0, std::iter::empty(), None); @@ -70,12 +55,12 @@ fn check_module() { fn first_block_epoch_zero_start() { new_test_ext(vec![0, 1, 2, 3]).execute_with(|| { let genesis_slot = 100; - let first_vrf = [1; 32]; + let first_vrf = RawVRFOutput([1; 32]); let pre_digest = make_pre_digest( 0, genesis_slot, - first_vrf, - [0xff; 64], + first_vrf.clone(), + RawVRFProof([0xff; 64]), ); assert_eq!(Babe::genesis_slot(), 0); @@ -128,3 +113,24 @@ fn authority_index() { "Trivially invalid authorities are ignored") }) } + +#[test] +fn can_predict_next_epoch_change() { + new_test_ext(vec![]).execute_with(|| { + assert_eq!(::EpochDuration::get(), 3); + // this sets the genesis slot to 6; + go_to_block(1, 6); + assert_eq!(Babe::genesis_slot(), 6); + assert_eq!(Babe::current_slot(), 6); + assert_eq!(Babe::epoch_index(), 0); + + progress_to_block(5); + + assert_eq!(Babe::epoch_index(), 5 / 3); + assert_eq!(Babe::current_slot(), 10); + + // next epoch change will be at + assert_eq!(Babe::current_epoch_start(), 9); // next change will be 12, 2 slots from now + assert_eq!(Babe::next_expected_epoch_change(System::block_number()), Some(5 + 2)); + }) +} diff --git a/frame/balances/Cargo.toml b/frame/balances/Cargo.toml index 6bb551c9c6fea03a747e700e5aac6f0d8156f300..f8a59ad15476fbd0c4f69fc418c17c67be3a7506 100644 --- a/frame/balances/Cargo.toml +++ b/frame/balances/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-balances" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,17 +10,17 @@ description = "FRAME pallet to manage balances" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "2.0.0-alpha.2", default-features = false, path = "../benchmarking", optional = true } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -pallet-transaction-payment = { version = "2.0.0-alpha.2", path = "../transaction-payment" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +pallet-transaction-payment = { version = "2.0.0-alpha.5", path = "../transaction-payment" } [features] default = ["std"] @@ -35,3 +35,6 @@ std = [ "frame-system/std", ] runtime-benchmarks = ["frame-benchmarking"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/balances/src/benchmarking.rs b/frame/balances/src/benchmarking.rs index 2473ce292004754574a96be0d07c3d2af3c038a5..161fdab96b934a5bf234604406c554b261fb7907 100644 --- a/frame/balances/src/benchmarking.rs +++ b/frame/balances/src/benchmarking.rs @@ -16,6 +16,8 @@ //! Balances pallet benchmarking. +#![cfg(feature = "runtime-benchmarks")] + use super::*; use frame_system::RawOrigin; @@ -116,4 +118,4 @@ benchmarks! { let balance_amount = existential_deposit.saturating_mul(e.into()); let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); }: set_balance(RawOrigin::Root, user_lookup, 0.into(), 0.into()) -} \ No newline at end of file +} diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 6e707caaa1a6480c4e9d447186fe572c173d8cf3..39e15b3f4f585d38b3d68752b1803782ae0b3619 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -148,14 +148,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(test)] -mod tests_local; -#[cfg(test)] -mod tests_composite; -#[cfg(test)] #[macro_use] mod tests; -#[cfg(feature = "runtime-benchmarks")] +mod tests_local; +mod tests_composite; mod benchmarking; use sp_std::prelude::*; @@ -167,7 +163,7 @@ use frame_support::{ Currency, OnKilledAccount, OnUnbalanced, TryDrop, StoredMap, WithdrawReason, WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement, Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive, - ExistenceRequirement::AllowDeath, IsDeadAccount, BalanceStatus as Status, MigrateAccount, + ExistenceRequirement::AllowDeath, IsDeadAccount, BalanceStatus as Status, } }; use sp_runtime::{ @@ -531,12 +527,6 @@ decl_module! { } } -impl, I: Instance> MigrateAccount for Module { - fn migrate_account(account: &T::AccountId) { - Locks::::migrate_key_from_blake(account); - } -} - impl, I: Instance> Module { // PRIVATE MUTABLES @@ -856,7 +846,7 @@ impl, I: Instance> frame_system::Trait for ElevatedTrait { type AvailableBlockRatio = T::AvailableBlockRatio; type Version = T::Version; type ModuleToIndex = T::ModuleToIndex; - type MigrateAccount = (); type OnNewAccount = T::OnNewAccount; + type OnNewAccount = T::OnNewAccount; type OnKilledAccount = T::OnKilledAccount; type AccountData = T::AccountData; } diff --git a/frame/balances/src/tests.rs b/frame/balances/src/tests.rs index 98c7c856bc88a3e74c56c64e77107bc57bacd500..8055c2013eaebc5de75e8d1a1b53c22dd87dc62e 100644 --- a/frame/balances/src/tests.rs +++ b/frame/balances/src/tests.rs @@ -16,6 +16,8 @@ //! Macro for creating the tests for the module. +#![cfg(test)] + #[macro_export] macro_rules! decl_tests { ($test:ty, $ext_builder:ty, $existential_deposit:expr) => { diff --git a/frame/balances/src/tests_composite.rs b/frame/balances/src/tests_composite.rs index 8bf31ec36ca40fb95c043aac8cf58d8d7822af83..8935dc4c9a12364d30c6f74a2bc5076306c38223 100644 --- a/frame/balances/src/tests_composite.rs +++ b/frame/balances/src/tests_composite.rs @@ -16,6 +16,8 @@ //! Test utilities +#![cfg(test)] + use sp_runtime::{Perbill, traits::{ConvertInto, IdentityLookup}, testing::Header}; use sp_core::H256; use sp_io; @@ -66,7 +68,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = super::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } parameter_types! { diff --git a/frame/balances/src/tests_local.rs b/frame/balances/src/tests_local.rs index c6beec07824409af6b87d5fc626a02344149faf1..c8a4a298f57fd90abcf0db63ed1e0d0b1323857f 100644 --- a/frame/balances/src/tests_local.rs +++ b/frame/balances/src/tests_local.rs @@ -16,6 +16,8 @@ //! Test utilities +#![cfg(test)] + use sp_runtime::{Perbill, traits::{ConvertInto, IdentityLookup}, testing::Header}; use sp_core::H256; use sp_io; @@ -66,7 +68,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = super::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = Module; } parameter_types! { diff --git a/frame/benchmark/Cargo.toml b/frame/benchmark/Cargo.toml index bf237d992cd64cecc2c771a1bec4af4ffc02002d..804a28370449c46056caf5b4e97a515f6e33b9c7 100644 --- a/frame/benchmark/Cargo.toml +++ b/frame/benchmark/Cargo.toml @@ -1,19 +1,22 @@ [package] name = "pallet-benchmark" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "Patterns to benchmark in a FRAME runtime." [dependencies] serde = { version = "1.0.101", optional = true } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.3", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.3", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.3", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.3", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.3", default-features = false, path = "../system" } -frame-benchmarking = { version = "2.0.0-alpha.3", default-features = false, path = "../benchmarking" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } [features] default = ["std"] @@ -27,3 +30,7 @@ std = [ "frame-system/std", "frame-benchmarking/std", ] +runtime-benchmarks = ["frame-benchmarking"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/benchmark/src/benchmarking.rs b/frame/benchmark/src/benchmarking.rs index 29f9e8ee972a5bf95dd774eaa0cc6dda316e8de1..1e4740da2c0595b45be30c4a1dc2496176f7d157 100644 --- a/frame/benchmark/src/benchmarking.rs +++ b/frame/benchmark/src/benchmarking.rs @@ -16,6 +16,8 @@ //! Benchmarks for common FRAME Pallet operations. +#![cfg(feature = "runtime-benchmarks")] + use super::*; use frame_system::RawOrigin; diff --git a/frame/benchmark/src/lib.rs b/frame/benchmark/src/lib.rs index a519e98930fc29e2ffd18a264f9ee7060d57af08..b571ffb5b9cab6272908585270a44e86efa48e6d 100644 --- a/frame/benchmark/src/lib.rs +++ b/frame/benchmark/src/lib.rs @@ -26,7 +26,7 @@ use frame_system::{self as system, ensure_signed}; use codec::{Encode, Decode}; use sp_std::prelude::Vec; -pub mod benchmarking; +mod benchmarking; /// Type alias for currency balance. pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -70,6 +70,7 @@ decl_module! { fn deposit_event() = default; /// Do nothing. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn do_nothing(_origin, input: u32) { if input > 0 { return Ok(()); @@ -81,6 +82,7 @@ decl_module! { /// storage database, however, the `repeat` calls will all pull from the /// storage overlay cache. You must consider this when analyzing the /// results of the benchmark. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn read_value(_origin, repeat: u32) { for _ in 0..repeat { MyValue::get(); @@ -88,6 +90,7 @@ decl_module! { } /// Put a value into a storage value. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn put_value(_origin, repeat: u32) { for r in 0..repeat { MyValue::put(r); @@ -99,6 +102,7 @@ decl_module! { /// storage database, however, the `repeat` calls will all pull from the /// storage overlay cache. You must consider this when analyzing the /// results of the benchmark. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn exists_value(_origin, repeat: u32) { for _ in 0..repeat { MyValue::exists(); @@ -106,6 +110,7 @@ decl_module! { } /// Remove a value from storage `repeat` number of times. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn remove_value(_origin, repeat: u32) { for r in 0..repeat { MyMap::remove(r); @@ -113,6 +118,7 @@ decl_module! { } /// Read a value from storage map `repeat` number of times. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn read_map(_origin, repeat: u32) { for r in 0..repeat { MyMap::get(r); @@ -120,6 +126,7 @@ decl_module! { } /// Insert a value into a map. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn insert_map(_origin, repeat: u32) { for r in 0..repeat { MyMap::insert(r, r); @@ -127,6 +134,7 @@ decl_module! { } /// Check is a map contains a value `repeat` number of times. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn contains_key_map(_origin, repeat: u32) { for r in 0..repeat { MyMap::contains_key(r); @@ -134,25 +142,29 @@ decl_module! { } /// Read a value from storage `repeat` number of times. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn remove_prefix(_origin, repeat: u32) { for r in 0..repeat { MyDoubleMap::remove_prefix(r); } } - // Add user to the list. + /// Add user to the list. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn add_member_list(origin) { let who = ensure_signed(origin)?; MyMemberList::::mutate(|x| x.push(who)); } - // Append user to the list. + /// Append user to the list. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn append_member_list(origin) { let who = ensure_signed(origin)?; MyMemberList::::append(&[who])?; } - // Encode a vector of accounts to bytes. + /// Encode a vector of accounts to bytes. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn encode_accounts(_origin, accounts: Vec) { let bytes = accounts.encode(); @@ -163,7 +175,8 @@ decl_module! { } } - // Decode bytes into a vector of accounts. + /// Decode bytes into a vector of accounts. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn decode_accounts(_origin, bytes: Vec) { let accounts: Vec = Decode::decode(&mut bytes.as_slice()).map_err(|_| "Could not decode")?; diff --git a/frame/benchmarking/Cargo.toml b/frame/benchmarking/Cargo.toml index b39031a6b77f4422b6ea558d0644201cdf8a3b8d..7ed60664190ee53bfe21574ad67133aca99de639 100644 --- a/frame/benchmarking/Cargo.toml +++ b/frame/benchmarking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-benchmarking" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,14 +9,15 @@ repository = "https://github.com/paritytech/substrate/" description = "Macro for benchmarking a FRAME runtime." [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -sp-api = { version = "2.0.0-alpha.2", path = "../../primitives/api", default-features = false } -sp-runtime-interface = { version = "2.0.0-alpha.2", path = "../../primitives/runtime-interface", default-features = false } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime", default-features = false } -sp-std = { version = "2.0.0-alpha.2", path = "../../primitives/std", default-features = false } -sp-io = { path = "../../primitives/io", default-features = false, version = "2.0.0-alpha.2" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +linregress = "0.1" +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +sp-api = { version = "2.0.0-alpha.5", path = "../../primitives/api", default-features = false } +sp-runtime-interface = { version = "2.0.0-alpha.5", path = "../../primitives/runtime-interface", default-features = false } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime", default-features = false } +sp-std = { version = "2.0.0-alpha.5", path = "../../primitives/std", default-features = false } +sp-io = { path = "../../primitives/io", default-features = false, version = "2.0.0-alpha.5"} +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [features] default = [ "std" ] @@ -29,3 +30,6 @@ std = [ "frame-support/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/benchmarking/src/analysis.rs b/frame/benchmarking/src/analysis.rs new file mode 100644 index 0000000000000000000000000000000000000000..fdf1210832cadc2b06f870ee2373b36d278e2109 --- /dev/null +++ b/frame/benchmarking/src/analysis.rs @@ -0,0 +1,243 @@ +// Copyright 2020 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 . + +//! Tools for analysing the benchmark results. + +use std::collections::BTreeMap; +use linregress::{FormulaRegressionBuilder, RegressionDataBuilder, RegressionModel}; +use crate::BenchmarkResults; + +pub struct Analysis { + base: u128, + slopes: Vec, + names: Vec, + value_dists: Option, u128, u128)>>, + model: Option, +} + +impl Analysis { + pub fn median_slopes(r: &Vec) -> Option { + let results = r[0].0.iter().enumerate().map(|(i, &(param, _))| { + let mut counted = BTreeMap::, usize>::new(); + for (params, _, _) in r.iter() { + let mut p = params.iter().map(|x| x.1).collect::>(); + p[i] = 0; + *counted.entry(p).or_default() += 1; + } + let others: Vec = counted.iter().max_by_key(|i| i.1).expect("r is not empty; qed").0.clone(); + let values = r.iter() + .filter(|v| + v.0.iter() + .map(|x| x.1) + .zip(others.iter()) + .enumerate() + .all(|(j, (v1, v2))| j == i || v1 == *v2) + ).map(|(ps, v, _)| (ps[i].1, *v)) + .collect::>(); + (format!("{:?}", param), i, others, values) + }).collect::>(); + + let models = results.iter().map(|(_, _, _, ref values)| { + let mut slopes = vec![]; + for (i, &(x1, y1)) in values.iter().enumerate() { + for &(x2, y2) in values.iter().skip(i + 1) { + if x1 != x2 { + slopes.push((y1 as f64 - y2 as f64) / (x1 as f64 - x2 as f64)); + } + } + } + slopes.sort_by(|a, b| a.partial_cmp(b).expect("values well defined; qed")); + let slope = slopes[slopes.len() / 2]; + + let mut offsets = vec![]; + for &(x, y) in values.iter() { + offsets.push(y as f64 - slope * x as f64); + } + offsets.sort_by(|a, b| a.partial_cmp(b).expect("values well defined; qed")); + let offset = offsets[offsets.len() / 2]; + + (offset, slope) + }).collect::>(); + + let models = models.iter() + .zip(results.iter()) + .map(|((offset, slope), (_, i, others, _))| { + let over = others.iter() + .enumerate() + .filter(|(j, _)| j != i) + .map(|(j, v)| models[j].1 * *v as f64) + .fold(0f64, |acc, i| acc + i); + (*offset - over, *slope) + }) + .collect::>(); + + let base = models[0].0.max(0f64) as u128; + let slopes = models.iter().map(|x| x.1.max(0f64) as u128).collect::>(); + + Some(Self { + base, + slopes, + names: results.into_iter().map(|x| x.0).collect::>(), + value_dists: None, + model: None, + }) + } + + pub fn min_squares_iqr(r: &Vec) -> Option { + let mut results = BTreeMap::, Vec>::new(); + for &(ref params, t, _) in r.iter() { + let p = params.iter().map(|x| x.1).collect::>(); + results.entry(p).or_default().push(t); + } + for (_, rs) in results.iter_mut() { + rs.sort(); + let ql = rs.len() / 4; + *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].0.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)| { + let total = vs.iter() + .fold(0u128, |acc, v| acc + *v); + let mean = total / vs.len() as u128; + let sum_sq_diff = vs.iter() + .fold(0u128, |acc, v| { + let d = mean.max(*v) - mean.min(*v); + acc + d * d + }); + let stddev = (sum_sq_diff as f64 / vs.len() as f64).sqrt() as u128; + (p.clone(), mean, stddev) + }).collect::>(); + + Some(Self { + base: (model.parameters.intercept_value + 0.5) as u128, + slopes, + names, + value_dists: Some(value_dists), + model: Some(model), + }) + } +} + +fn ms(mut nanos: u128) -> String { + let mut x = 100_000u128; + while x > 1 { + if nanos > x * 1_000 { + nanos = nanos / x * x; + break; + } + x /= 10; + } + format!("{}", nanos as f64 / 1_000f64) +} + +impl std::fmt::Display for Analysis { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(ref value_dists) = self.value_dists { + writeln!(f, "\nData points distribution:")?; + writeln!(f, "{} mean µs sigma µs %", self.names.iter().map(|p| format!("{:>5}", p)).collect::>().join(" "))?; + for (param_values, mean, sigma) in value_dists.iter() { + writeln!(f, "{} {:>8} {:>8} {:>3}.{}%", + param_values.iter().map(|v| format!("{:>5}", v)).collect::>().join(" "), + ms(*mean), + ms(*sigma), + (sigma * 100 / mean), + (sigma * 1000 / mean % 10) + )?; + } + } + if let Some(ref model) = self.model { + 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))?; + } + } + + writeln!(f, "\nModel:")?; + writeln!(f, "Time ~= {:>8}", ms(self.base))?; + for (&t, n) in self.slopes.iter().zip(self.names.iter()) { + writeln!(f, " + {} {:>8}", n, ms(t))?; + } + writeln!(f, " µs") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::BenchmarkParameter; + + #[test] + fn analysis_median_slopes_should_work() { + let a = Analysis::median_slopes(&vec![ + (vec![(BenchmarkParameter::n, 1), (BenchmarkParameter::m, 5)], 11_500_000, 0), + (vec![(BenchmarkParameter::n, 2), (BenchmarkParameter::m, 5)], 12_500_000, 0), + (vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 5)], 13_500_000, 0), + (vec![(BenchmarkParameter::n, 4), (BenchmarkParameter::m, 5)], 14_500_000, 0), + (vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 1)], 13_100_000, 0), + (vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 3)], 13_300_000, 0), + (vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 7)], 13_700_000, 0), + (vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 10)], 14_000_000, 0), + ]).unwrap(); + assert_eq!(a.base, 10_000_000); + assert_eq!(a.slopes, vec![1_000_000, 100_000]); + } + + #[test] + fn analysis_median_min_squares_should_work() { + let a = Analysis::min_squares_iqr(&vec![ + (vec![(BenchmarkParameter::n, 1), (BenchmarkParameter::m, 5)], 11_500_000, 0), + (vec![(BenchmarkParameter::n, 2), (BenchmarkParameter::m, 5)], 12_500_000, 0), + (vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 5)], 13_500_000, 0), + (vec![(BenchmarkParameter::n, 4), (BenchmarkParameter::m, 5)], 14_500_000, 0), + (vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 1)], 13_100_000, 0), + (vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 3)], 13_300_000, 0), + (vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 7)], 13_700_000, 0), + (vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 10)], 14_000_000, 0), + ]).unwrap(); + assert_eq!(a.base, 10_000_000); + assert_eq!(a.slopes, vec![1_000_000, 100_000]); + } +} diff --git a/frame/benchmarking/src/lib.rs b/frame/benchmarking/src/lib.rs index a18048d3053370ca4f89461d83436e00d1a1af74..4cd6072ce4555f20025e788830fa1e9b588cc6ec 100644 --- a/frame/benchmarking/src/lib.rs +++ b/frame/benchmarking/src/lib.rs @@ -20,7 +20,12 @@ mod tests; mod utils; +#[cfg(feature = "std")] +mod analysis; + pub use utils::*; +#[cfg(feature = "std")] +pub use analysis::Analysis; #[doc(hidden)] pub use sp_io::storage::root as storage_root; pub use sp_runtime::traits::Dispatchable; @@ -129,7 +134,23 @@ macro_rules! benchmarks { } $( $rest:tt )* ) => { - $crate::benchmarks_iter!({ + $crate::benchmarks_iter!(NO_INSTANCE { + $( { $common , $common_from , $common_to , $common_instancer } )* + } ( ) $( $rest )* ); + } +} + +#[macro_export] +macro_rules! benchmarks_instance { + ( + _ { + $( + let $common:ident in $common_from:tt .. $common_to:expr => $common_instancer:expr; + )* + } + $( $rest:tt )* + ) => { + $crate::benchmarks_iter!(INSTANCE { $( { $common , $common_from , $common_to , $common_instancer } )* } ( ) $( $rest )* ); } @@ -140,44 +161,63 @@ macro_rules! benchmarks { macro_rules! benchmarks_iter { // mutation arm: ( + $instance:ident { $( $common:tt )* } ( $( $names:ident )* ) $name:ident { $( $code:tt )* }: _ ( $origin:expr $( , $arg:expr )* ) $( $rest:tt )* ) => { $crate::benchmarks_iter! { - { $( $common )* } ( $( $names )* ) $name { $( $code )* }: $name ( $origin $( , $arg )* ) $( $rest )* + $instance { $( $common )* } ( $( $names )* ) $name { $( $code )* }: $name ( $origin $( , $arg )* ) $( $rest )* } }; - // mutation arm: + // no instance mutation arm: ( + NO_INSTANCE { $( $common:tt )* } ( $( $names:ident )* ) $name:ident { $( $code:tt )* }: $dispatch:ident ( $origin:expr $( , $arg:expr )* ) $( $rest:tt )* ) => { $crate::benchmarks_iter! { - { $( $common )* } ( $( $names )* ) $name { $( $code )* }: { + NO_INSTANCE + { $( $common )* } ( $( $names )* ) $name { $( $code )* }: { as $crate::Dispatchable>::dispatch(Call::::$dispatch($($arg),*), $origin.into())?; } $( $rest )* } }; + // instance mutation arm: + ( + INSTANCE + { $( $common:tt )* } + ( $( $names:ident )* ) + $name:ident { $( $code:tt )* }: $dispatch:ident ( $origin:expr $( , $arg:expr )* ) + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + INSTANCE + { $( $common )* } ( $( $names )* ) $name { $( $code )* }: { + as $crate::Dispatchable>::dispatch(Call::::$dispatch($($arg),*), $origin.into())?; + } $( $rest )* + } + }; // iteration arm: ( + $instance:ident { $( $common:tt )* } ( $( $names:ident )* ) $name:ident { $( $code:tt )* }: $eval:block $( $rest:tt )* ) => { $crate::benchmark_backend! { - $name { $( $common )* } { } { $eval } { $( $code )* } + $instance $name { $( $common )* } { } { $eval } { $( $code )* } } - $crate::benchmarks_iter!( { $( $common )* } ( $( $names )* $name ) $( $rest )* ); + $crate::benchmarks_iter!( $instance { $( $common )* } ( $( $names )* $name ) $( $rest )* ); }; // iteration-exit arm - ( { $( $common:tt )* } ( $( $names:ident )* ) ) => { - $crate::selected_benchmark!( $( $names ),* ); - $crate::impl_benchmark!( $( $names ),* ); + ( $instance:ident { $( $common:tt )* } ( $( $names:ident )* ) ) => { + $crate::selected_benchmark!( $instance $( $names ),* ); + $crate::impl_benchmark!( $instance $( $names ),* ); } } @@ -185,7 +225,7 @@ macro_rules! benchmarks_iter { #[allow(missing_docs)] macro_rules! benchmark_backend { // parsing arms - ($name:ident { + ($instance:ident $name:ident { $( $common:tt )* } { $( PRE { $( $pre_parsed:tt )* } )* @@ -194,13 +234,13 @@ macro_rules! benchmark_backend { $( $rest:tt )* } ) => { $crate::benchmark_backend! { - $name { $( $common )* } { + $instance $name { $( $common )* } { $( PRE { $( $pre_parsed )* } )* PRE { $pre_id , $pre_ty , $pre_ex } } { $eval } { $( $rest )* } } }; - ($name:ident { + ($instance:ident $name:ident { $( $common:tt )* } { $( $parsed:tt )* @@ -209,14 +249,14 @@ macro_rules! benchmark_backend { $( $rest:tt )* }) => { $crate::benchmark_backend! { - $name { $( $common )* } { + $instance $name { $( $common )* } { $( $parsed )* PARAM { $param , $param_from , $param_to , $param_instancer } } { $eval } { $( $rest )* } } }; // mutation arm to look after defaulting to a common param - ($name:ident { + ($instance:ident $name:ident { $( { $common:ident , $common_from:tt , $common_to:expr , $common_instancer:expr } )* } { $( $parsed:tt )* @@ -225,7 +265,7 @@ macro_rules! benchmark_backend { $( $rest:tt )* }) => { $crate::benchmark_backend! { - $name { + $instance $name { $( { $common , $common_from , $common_to , $common_instancer } )* } { $( $parsed )* @@ -239,7 +279,7 @@ macro_rules! benchmark_backend { } }; // mutation arm to look after defaulting only the range to common param - ($name:ident { + ($instance:ident $name:ident { $( { $common:ident , $common_from:tt , $common_to:expr , $common_instancer:expr } )* } { $( $parsed:tt )* @@ -248,7 +288,7 @@ macro_rules! benchmark_backend { $( $rest:tt )* }) => { $crate::benchmark_backend! { - $name { + $instance $name { $( { $common , $common_from , $common_to , $common_instancer } )* } { $( $parsed )* @@ -262,7 +302,7 @@ macro_rules! benchmark_backend { } }; // mutation arm to look after a single tt for param_from. - ($name:ident { + ($instance:ident $name:ident { $( $common:tt )* } { $( $parsed:tt )* @@ -271,14 +311,14 @@ macro_rules! benchmark_backend { $( $rest:tt )* }) => { $crate::benchmark_backend! { - $name { $( $common )* } { $( $parsed )* } { $eval } { + $instance $name { $( $common )* } { $( $parsed )* } { $eval } { let $param in ( $param_from ) .. $param_to => $param_instancer; $( $rest )* } } }; // mutation arm to look after the default tail of `=> ()` - ($name:ident { + ($instance:ident $name:ident { $( $common:tt )* } { $( $parsed:tt )* @@ -287,14 +327,14 @@ macro_rules! benchmark_backend { $( $rest:tt )* }) => { $crate::benchmark_backend! { - $name { $( $common )* } { $( $parsed )* } { $eval } { + $instance $name { $( $common )* } { $( $parsed )* } { $eval } { let $param in $param_from .. $param_to => (); $( $rest )* } } }; // mutation arm to look after `let _ =` - ($name:ident { + ($instance:ident $name:ident { $( $common:tt )* } { $( $parsed:tt )* @@ -303,14 +343,14 @@ macro_rules! benchmark_backend { $( $rest:tt )* }) => { $crate::benchmark_backend! { - $name { $( $common )* } { $( $parsed )* } { $eval } { + $instance $name { $( $common )* } { $( $parsed )* } { $eval } { let $pre_id : _ = $pre_ex; $( $rest )* } } }; - // actioning arm - ($name:ident { + // no instance actioning arm + (NO_INSTANCE $name:ident { $( { $common:ident , $common_from:tt , $common_to:expr , $common_instancer:expr } )* } { $( PRE { $pre_id:tt , $pre_ty:ty , $pre_ex:expr } )* @@ -328,6 +368,45 @@ macro_rules! benchmark_backend { ] } + fn instance(&self, components: &[($crate::BenchmarkParameter, u32)]) + -> Result Result<(), &'static str>>, &'static str> + { + $( + let $common = $common_from; + )* + $( + // Prepare instance + let $param = components.iter().find(|&c| c.0 == $crate::BenchmarkParameter::$param).unwrap().1; + )* + $( + let $pre_id : $pre_ty = $pre_ex; + )* + $( $param_instancer ; )* + $( $post )* + + Ok(Box::new(move || -> Result<(), &'static str> { $eval; Ok(()) })) + } + } + }; + // instance actioning arm + (INSTANCE $name:ident { + $( { $common:ident , $common_from:tt , $common_to:expr , $common_instancer:expr } )* + } { + $( PRE { $pre_id:tt , $pre_ty:ty , $pre_ex:expr } )* + $( PARAM { $param:ident , $param_from:expr , $param_to:expr , $param_instancer:expr } )* + } { $eval:block } { $( $post:tt )* } ) => { + #[allow(non_camel_case_types)] + struct $name; + #[allow(unused_variables)] + impl, I: Instance> $crate::BenchmarkingSetupInstance for $name { + fn components(&self) -> Vec<($crate::BenchmarkParameter, u32, u32)> { + vec! [ + $( + ($crate::BenchmarkParameter::$param, $param_from, $param_to) + ),* + ] + } + fn instance(&self, components: &[($crate::BenchmarkParameter, u32)]) -> Result Result<(), &'static str>>, &'static str> { @@ -367,7 +446,7 @@ macro_rules! benchmark_backend { #[macro_export] macro_rules! selected_benchmark { ( - $( $bench:ident ),* + NO_INSTANCE $( $bench:ident ),* ) => { // The list of available benchmarks for this pallet. #[allow(non_camel_case_types)] @@ -392,23 +471,53 @@ macro_rules! selected_benchmark { } } }; + ( + INSTANCE $( $bench:ident ),* + ) => { + // The list of available benchmarks for this pallet. + #[allow(non_camel_case_types)] + enum SelectedBenchmark { + $( $bench, )* + } + + // Allow us to select a benchmark from the list of available benchmarks. + impl, I: Instance> $crate::BenchmarkingSetupInstance for SelectedBenchmark { + fn components(&self) -> Vec<($crate::BenchmarkParameter, u32, u32)> { + match self { + $( Self::$bench => <$bench as $crate::BenchmarkingSetupInstance>::components(&$bench), )* + } + } + + fn instance(&self, components: &[($crate::BenchmarkParameter, u32)]) + -> Result Result<(), &'static str>>, &'static str> + { + match self { + $( Self::$bench => <$bench as $crate::BenchmarkingSetupInstance>::instance(&$bench, components), )* + } + } + } + } } #[macro_export] macro_rules! impl_benchmark { ( - $( $name:ident ),* + NO_INSTANCE $( $name:ident ),* ) => { impl $crate::Benchmarking<$crate::BenchmarkResults> for Module { + fn benchmarks() -> Vec<&'static [u8]> { + vec![ $( stringify!($name).as_ref() ),* ] + } + fn run_benchmark( - extrinsic: Vec, - lowest_range_values: Vec, - highest_range_values: Vec, - steps: Vec, + extrinsic: &[u8], + lowest_range_values: &[u32], + highest_range_values: &[u32], + steps: &[u32], repeat: u32, ) -> Result, &'static str> { // Map the input to the selected benchmark. - let extrinsic = sp_std::str::from_utf8(extrinsic.as_slice()) + let extrinsic = sp_std::str::from_utf8(extrinsic) .map_err(|_| "`extrinsic` is not a valid utf8 string!")?; let selected_benchmark = match extrinsic { $( stringify!($name) => SelectedBenchmark::$name, )* @@ -487,5 +596,163 @@ macro_rules! impl_benchmark { return Ok(results); } } + }; + ( + INSTANCE $( $name:ident ),* + ) => { + impl, I: Instance> $crate::Benchmarking<$crate::BenchmarkResults> for Module { + fn benchmarks() -> Vec<&'static [u8]> { + vec![ $( stringify!($name).as_ref() ),* ] + } + + fn run_benchmark( + extrinsic: &[u8], + lowest_range_values: &[u32], + highest_range_values: &[u32], + steps: &[u32], + repeat: u32, + ) -> Result, &'static str> { + // Map the input to the selected benchmark. + let extrinsic = sp_std::str::from_utf8(extrinsic) + .map_err(|_| "`extrinsic` is not a valid utf8 string!")?; + let selected_benchmark = match extrinsic { + $( stringify!($name) => SelectedBenchmark::$name, )* + _ => return Err("Could not find extrinsic."), + }; + + // Warm up the DB + $crate::benchmarking::commit_db(); + $crate::benchmarking::wipe_db(); + + let components = >::components(&selected_benchmark); + let mut results: Vec<$crate::BenchmarkResults> = Vec::new(); + + // Default number of steps for a component. + let mut prev_steps = 10; + + // Select the component we will be benchmarking. Each component will be benchmarked. + for (idx, (name, low, high)) in components.iter().enumerate() { + // Get the number of steps for this component. + let steps = steps.get(idx).cloned().unwrap_or(prev_steps); + prev_steps = steps; + + let lowest = lowest_range_values.get(idx).cloned().unwrap_or(*low); + let highest = highest_range_values.get(idx).cloned().unwrap_or(*high); + + let diff = highest - lowest; + + // Create up to `STEPS` steps for that component between high and low. + let step_size = (diff / steps).max(1); + let num_of_steps = diff / step_size + 1; + + for s in 0..num_of_steps { + // This is the value we will be testing for component `name` + let component_value = lowest + step_size * s; + + // Select the max value for all the other components. + let c: Vec<($crate::BenchmarkParameter, u32)> = components.iter() + .enumerate() + .map(|(idx, (n, _, h))| + if n == name { + (*n, component_value) + } else { + (*n, *highest_range_values.get(idx).unwrap_or(h)) + } + ) + .collect(); + + // Run the benchmark `repeat` times. + for _ in 0..repeat { + // Set up the externalities environment for the setup we want to benchmark. + let closure_to_benchmark = >::instance(&selected_benchmark, &c)?; + + // Commit the externalities to the database, flushing the DB cache. + // This will enable worst case scenario for reading from the database. + $crate::benchmarking::commit_db(); + + // Time the extrinsic logic. + let start_extrinsic = $crate::benchmarking::current_time(); + closure_to_benchmark()?; + let finish_extrinsic = $crate::benchmarking::current_time(); + let elapsed_extrinsic = finish_extrinsic - start_extrinsic; + + // Time the storage root recalculation. + let start_storage_root = $crate::benchmarking::current_time(); + $crate::storage_root(); + let finish_storage_root = $crate::benchmarking::current_time(); + let elapsed_storage_root = finish_storage_root - start_storage_root; + + results.push((c.clone(), elapsed_extrinsic, elapsed_storage_root)); + + // Wipe the DB back to the genesis state. + $crate::benchmarking::wipe_db(); + } + } + } + return Ok(results); + } + } } } + + +/// This macro adds pallet benchmarks to a `Vec` object. +/// +/// First create an object that holds in the input parameters for the benchmark: +/// +/// ```ignore +/// let params = (&pallet, &benchmark, &lowest_range_values, &highest_range_values, &steps, repeat); +/// ``` +/// +/// Then define a mutable local variable to hold your `BenchmarkBatch` object: +/// +/// ```ignore +/// let mut batches = Vec::::new(); +/// ```` +/// +/// Then add the pallets you want to benchmark to this object, including the string +/// you want to use target a particular pallet: +/// +/// ```ignore +/// add_benchmark!(params, batches, b"balances", Balances); +/// add_benchmark!(params, batches, b"identity", Identity); +/// add_benchmark!(params, batches, b"session", SessionBench::); +/// ... +/// ``` +/// +/// At the end of `dispatch_benchmark`, you should return this batches object. +#[macro_export] +macro_rules! add_benchmark { + ( $params:ident, $batches:ident, $name:literal, $( $location:tt )* ) => ( + let (pallet, benchmark, lowest_range_values, highest_range_values, steps, repeat) = $params; + if &pallet[..] == &$name[..] || &pallet[..] == &b"*"[..] { + if &pallet[..] == &b"*"[..] || &benchmark[..] == &b"*"[..] { + for benchmark in $( $location )*::benchmarks().into_iter() { + $batches.push($crate::BenchmarkBatch { + results: $( $location )*::run_benchmark( + benchmark, + &lowest_range_values[..], + &highest_range_values[..], + &steps[..], + repeat, + )?, + pallet: pallet.to_vec(), + benchmark: benchmark.to_vec(), + }); + } + } else { + $batches.push($crate::BenchmarkBatch { + results: $( $location )*::run_benchmark( + &benchmark[..], + &lowest_range_values[..], + &highest_range_values[..], + &steps[..], + repeat, + )?, + pallet: pallet.to_vec(), + benchmark: benchmark.clone(), + }); + } + } + ) +} diff --git a/frame/benchmarking/src/tests.rs b/frame/benchmarking/src/tests.rs index 6e837ec241334bb6f4a1cf6bdddb5ce17095d2af..b3537617c7282da135db2c9ea59c6ff6506b3487 100644 --- a/frame/benchmarking/src/tests.rs +++ b/frame/benchmarking/src/tests.rs @@ -27,11 +27,13 @@ use frame_system::{RawOrigin, ensure_signed, ensure_none}; decl_module! { pub struct Module for enum Call where origin: T::Origin { + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn dummy(origin, _n: u32) -> DispatchResult { let _sender = ensure_signed(origin)?; Ok(()) } + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn other_dummy(origin, _n: u32) -> DispatchResult { let _sender = ensure_none(origin)?; Ok(()) @@ -71,7 +73,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/benchmarking/src/utils.rs b/frame/benchmarking/src/utils.rs index bc6cfbcc86ea1e221cdceb7728fd89b2e7bb2bd9..a6f262eab99ec87f9ecd9597fb411e0772587a1d 100644 --- a/frame/benchmarking/src/utils.rs +++ b/frame/benchmarking/src/utils.rs @@ -22,13 +22,24 @@ use sp_io::hashing::blake2_256; use sp_runtime::RuntimeString; /// An alphabet of possible parameters to use for benchmarking. -#[derive(codec::Encode, codec::Decode, Clone, Copy, PartialEq, Debug)] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Debug)] #[allow(missing_docs)] #[allow(non_camel_case_types)] pub enum BenchmarkParameter { a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, } +/// The results of a single of benchmark. +#[derive(Encode, Decode, Clone, PartialEq, Debug)] +pub struct BenchmarkBatch { + /// The pallet containing this benchmark. + pub pallet: Vec, + /// The extrinsic (or benchmark name) of this benchmark. + pub benchmark: Vec, + /// The results from this benchmark. + pub results: Vec, +} + /// Results 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. @@ -39,13 +50,13 @@ sp_api::decl_runtime_apis! { pub trait Benchmark { /// Dispatch the given benchmark. fn dispatch_benchmark( - module: Vec, - extrinsic: Vec, + pallet: Vec, + benchmark: Vec, lowest_range_values: Vec, highest_range_values: Vec, steps: Vec, repeat: u32, - ) -> Result, RuntimeString>; + ) -> Result, RuntimeString>; } } @@ -75,19 +86,24 @@ pub trait Benchmarking { /// The pallet benchmarking trait. pub trait Benchmarking { + /// Get the benchmarks available for this pallet. Generally there is one benchmark per + /// extrinsic, so these are sometimes just called "extrinsics". + fn benchmarks() -> Vec<&'static [u8]>; + /// Run the benchmarks for this pallet. /// /// Parameters - /// - `extrinsic`: The name of extrinsic function you want to benchmark encoded as bytes. + /// - `name`: The name of extrinsic function or benchmark you want to benchmark encoded as + /// bytes. /// - `steps`: The number of sample points you want to take across the range of parameters. /// - `lowest_range_values`: The lowest number for each range of parameters. /// - `highest_range_values`: The highest number for each range of parameters. /// - `repeat`: The number of times you want to repeat a benchmark. fn run_benchmark( - extrinsic: Vec, - lowest_range_values: Vec, - highest_range_values: Vec, - steps: Vec, + name: &[u8], + lowest_range_values: &[u32], + highest_range_values: &[u32], + steps: &[u32], repeat: u32, ) -> Result, &'static str>; } @@ -101,6 +117,15 @@ pub trait BenchmarkingSetup { fn instance(&self, components: &[(BenchmarkParameter, u32)]) -> Result Result<(), &'static str>>, &'static str>; } +/// The required setup for creating a benchmark. +pub trait BenchmarkingSetupInstance { + /// Return the components and their ranges which should be tested in this benchmark. + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)>; + + /// Set up the storage, and prepare a closure to test in a single run of the benchmark. + fn instance(&self, components: &[(BenchmarkParameter, u32)]) -> Result Result<(), &'static str>>, &'static str>; +} + /// Grab an account, seeded by a name and index. pub fn account(name: &'static str, index: u32, seed: u32) -> AccountId { let entropy = (name, index, seed).using_encoded(blake2_256); diff --git a/frame/collective/Cargo.toml b/frame/collective/Cargo.toml index 60899feb4fbb96aa7eb44d637c5cae43bd52612a..113705c2c8b90c95f068971bb0f810c7b401debc 100644 --- a/frame/collective/Cargo.toml +++ b/frame/collective/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-collective" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,17 +10,18 @@ description = "Collective system: Members of a set of account IDs can make their [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] hex-literal = "0.2.1" -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } [features] default = ["std"] @@ -34,3 +35,11 @@ std = [ "sp-runtime/std", "frame-system/std", ] +runtime-benchmarks = [ + "frame-benchmarking", + "sp-runtime/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/collective/src/benchmarking.rs b/frame/collective/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..51db4ee109b06bc550759574432b3170f630e907 --- /dev/null +++ b/frame/collective/src/benchmarking.rs @@ -0,0 +1,185 @@ +// Copyright 2020 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 . + +//! Staking pallet benchmarking. + +use super::*; + +use frame_system::RawOrigin as SystemOrigin; +use frame_benchmarking::{benchmarks_instance, account}; + +use frame_system::Module as System; +use crate::Module as Collective; + +const SEED: u32 = 0; + +benchmarks_instance! { + _{ + // User account seed. + let u in 1 .. 1000 => (); + // Old members. + let n in 1 .. 1000 => (); + // New members. + let m in 1 .. 1000 => (); + // Existing proposals. + let p in 1 .. 100 => (); + } + + set_members { + let m in ...; + let n in ...; + + // Construct `new_members`. + // It should influence timing since it will sort this vector. + let mut new_members = vec![]; + for i in 0 .. m { + let member = account("member", i, SEED); + new_members.push(member); + } + + // Set old members. + // We compute the difference of old and new members, so it should influence timing. + let mut old_members = vec![]; + for i in 0 .. n { + let old_member = account("old member", i, SEED); + old_members.push(old_member); + } + + let prime = Some(account("prime", 0, SEED)); + + Collective::::set_members(SystemOrigin::Root.into(), old_members, prime.clone())?; + + }: _(SystemOrigin::Root, new_members, prime) + + execute { + let u in ...; + + let caller: T::AccountId = account("caller", u, SEED); + let proposal: T::Proposal = Call::::close(Default::default(), Default::default()).into(); + + Collective::::set_members(SystemOrigin::Root.into(), vec![caller.clone()], None)?; + + }: _(SystemOrigin::Signed(caller), Box::new(proposal)) + + propose { + let u in ...; + + let caller: T::AccountId = account("caller", u, SEED); + let proposal: T::Proposal = Call::::close(Default::default(), Default::default()).into(); + + Collective::::set_members(SystemOrigin::Root.into(), vec![caller.clone()], None)?; + + let member_count = 0; + + }: _(SystemOrigin::Signed(caller), member_count, Box::new(proposal.into())) + + propose_else_branch { + let u in ...; + let p in ...; + + let caller: T::AccountId = account("caller", u, SEED); + let proposal: T::Proposal = Call::::close(Default::default(), Default::default()).into(); + + Collective::::set_members(SystemOrigin::Root.into(), vec![caller.clone()], None)?; + + let member_count = 3; + + // Add previous proposals. + for i in 0 .. p { + let proposal: T::Proposal = Call::::close(Default::default(), (i + 1).into()).into(); + Collective::::propose(SystemOrigin::Signed(caller.clone()).into(), member_count.clone(), Box::new(proposal.into()))?; + } + + }: propose(SystemOrigin::Signed(caller), member_count, Box::new(proposal.into())) + + vote { + let u in ...; + + let caller1: T::AccountId = account("caller1", u, SEED); + let caller2: T::AccountId = account("caller2", u, SEED); + + let proposal: Box = Box::new(Call::::close(Default::default(), Default::default()).into()); + let proposal_hash = T::Hashing::hash_of(&proposal); + + Collective::::set_members(SystemOrigin::Root.into(), vec![caller1.clone(), caller2.clone()], None)?; + + let member_count = 3; + Collective::::propose(SystemOrigin::Signed(caller1.clone()).into(), member_count, proposal)?; + + let index = 0; + let approve = true; + + }: _(SystemOrigin::Signed(caller2), proposal_hash, index, approve) + + vote_not_approve { + let u in ...; + + let caller1: T::AccountId = account("caller1", u, SEED); + let caller2: T::AccountId = account("caller2", u, SEED); + + let proposal: Box = Box::new(Call::::close(Default::default(), Default::default()).into()); + let proposal_hash = T::Hashing::hash_of(&proposal); + + Collective::::set_members(SystemOrigin::Root.into(), vec![caller1.clone(), caller2.clone()], None)?; + + let member_count = 3; + Collective::::propose(SystemOrigin::Signed(caller1.clone()).into(), member_count, proposal)?; + + let index = 0; + let approve = false; + + }: vote(SystemOrigin::Signed(caller2), proposal_hash, index, approve) + + vote_approved { + let u in ...; + + let caller1: T::AccountId = account("caller1", u, SEED); + let caller2: T::AccountId = account("caller2", u, SEED); + + let proposal: Box = Box::new(Call::::close(Default::default(), Default::default()).into()); + let proposal_hash = T::Hashing::hash_of(&proposal); + + Collective::::set_members(SystemOrigin::Root.into(), vec![caller1.clone(), caller2.clone()], None)?; + + let member_count = 2; + Collective::::propose(SystemOrigin::Signed(caller1.clone()).into(), member_count, proposal)?; + + let index = 0; + let approve = true; + + }: vote(SystemOrigin::Signed(caller2), proposal_hash, index, approve) + + close { + let u in ...; + + let caller1: T::AccountId = account("caller1", u, SEED); + let caller2: T::AccountId = account("caller2", u, SEED); + + let proposal: Box = Box::new(Call::::close(Default::default(), Default::default()).into()); + let proposal_hash = T::Hashing::hash_of(&proposal); + + Collective::::set_members(SystemOrigin::Root.into(), vec![caller1.clone(), caller2.clone()], None)?; + let member_count = 2; + Collective::::propose(SystemOrigin::Signed(caller1.clone()).into(), member_count, proposal)?; + + let index = 0; + let approve = true; + + let vote_end = T::MotionDuration::get() + 1u32.into(); + System::::set_block_number(vote_end); + + }: _(SystemOrigin::Signed(caller2), proposal_hash, index) +} diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 30b1b19974b5f7de227db4cbc62be88fd5f04905..d06d83b5bee6dc2460f9c5397bc827309f46e1a1 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -48,6 +48,9 @@ use frame_support::{ }; use frame_system::{self as system, ensure_signed, ensure_root}; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + /// Simple index type for proposal counting. pub type ProposalIndex = u32; @@ -57,12 +60,12 @@ pub type ProposalIndex = u32; /// vote exactly once, therefore also the number of votes for any given motion. pub type MemberCount = u32; -pub trait Trait: frame_system::Trait { +pub trait Trait: frame_system::Trait { /// The outer origin type. type Origin: From>; /// The outer call dispatch type. - type Proposal: Parameter + Dispatchable>::Origin>; + type Proposal: Parameter + Dispatchable>::Origin> + From>; /// The outer event type. type Event: From> + Into<::Event>; @@ -168,17 +171,6 @@ decl_error! { } } -mod migration { - use super::*; - - pub fn migrate, I: Instance>() { - for p in Proposals::::get().into_iter() { - ProposalOf::::migrate_key_from_blake(&p); - Voting::::migrate_key_from_blake(&p); - } - } -} - // Note: this module is not benchmarked. The weights are obtained based on the similarity of the // executed logic with other democracy function. Note that councillor operations are assigned to the // operational class. @@ -188,10 +180,6 @@ decl_module! { fn deposit_event() = default; - fn on_runtime_upgrade() { - migration::migrate::(); - } - /// Set the collective's membership. /// /// - `new_members`: The new member list. Be nice to the chain and @@ -446,7 +434,7 @@ where pub struct EnsureMember(sp_std::marker::PhantomData<(AccountId, I)>); impl< O: Into, O>> + From>, - AccountId, + AccountId: Default, I, > EnsureOrigin for EnsureMember { type Success = AccountId; @@ -456,6 +444,11 @@ impl< r => Err(O::from(r)), }) } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + O::from(RawOrigin::Member(Default::default())) + } } pub struct EnsureMembers(sp_std::marker::PhantomData<(N, AccountId, I)>); @@ -472,6 +465,11 @@ impl< r => Err(O::from(r)), }) } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + O::from(RawOrigin::Members(N::VALUE, N::VALUE)) + } } pub struct EnsureProportionMoreThan( @@ -491,6 +489,11 @@ impl< r => Err(O::from(r)), }) } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + O::from(RawOrigin::Members(1u32, 0u32)) + } } pub struct EnsureProportionAtLeast( @@ -510,6 +513,11 @@ impl< r => Err(O::from(r)), }) } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + O::from(RawOrigin::Members(0u32, 0u32)) + } } #[cfg(test)] @@ -550,7 +558,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } impl Trait for Test { @@ -582,19 +590,20 @@ mod tests { ); fn make_ext() -> sp_io::TestExternalities { - GenesisConfig { + let mut ext: sp_io::TestExternalities = GenesisConfig { collective_Instance1: Some(collective::GenesisConfig { members: vec![1, 2, 3], phantom: Default::default(), }), collective: None, - }.build_storage().unwrap().into() + }.build_storage().unwrap().into(); + ext.execute_with(|| System::set_block_number(1)); + ext } #[test] fn motions_basic_environment_works() { make_ext().execute_with(|| { - System::set_block_number(1); assert_eq!(Collective::members(), vec![1, 2, 3]); assert_eq!(Collective::proposals(), Vec::::new()); }); @@ -607,7 +616,6 @@ mod tests { #[test] fn close_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash = BlakeTwo256::hash_of(&proposal); @@ -623,7 +631,7 @@ mod tests { System::set_block_number(4); assert_ok!(Collective::close(Origin::signed(4), hash.clone(), 0)); - let record = |event| EventRecord { phase: Phase::Finalization, event, topics: vec![] }; + let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; assert_eq!(System::events(), vec![ record(Event::collective_Instance1(RawEvent::Proposed(1, 0, hash.clone(), 3))), record(Event::collective_Instance1(RawEvent::Voted(2, hash.clone(), true, 2, 0))), @@ -636,7 +644,6 @@ mod tests { #[test] fn close_with_prime_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash = BlakeTwo256::hash_of(&proposal); assert_ok!(Collective::set_members(Origin::ROOT, vec![1, 2, 3], Some(3))); @@ -647,7 +654,7 @@ mod tests { System::set_block_number(4); assert_ok!(Collective::close(Origin::signed(4), hash.clone(), 0)); - let record = |event| EventRecord { phase: Phase::Finalization, event, topics: vec![] }; + let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; assert_eq!(System::events(), vec![ record(Event::collective_Instance1(RawEvent::Proposed(1, 0, hash.clone(), 3))), record(Event::collective_Instance1(RawEvent::Voted(2, hash.clone(), true, 2, 0))), @@ -660,7 +667,6 @@ mod tests { #[test] fn close_with_voting_prime_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash = BlakeTwo256::hash_of(&proposal); assert_ok!(Collective::set_members(Origin::ROOT, vec![1, 2, 3], Some(1))); @@ -671,7 +677,7 @@ mod tests { System::set_block_number(4); assert_ok!(Collective::close(Origin::signed(4), hash.clone(), 0)); - let record = |event| EventRecord { phase: Phase::Finalization, event, topics: vec![] }; + let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; assert_eq!(System::events(), vec![ record(Event::collective_Instance1(RawEvent::Proposed(1, 0, hash.clone(), 3))), record(Event::collective_Instance1(RawEvent::Voted(2, hash.clone(), true, 2, 0))), @@ -685,7 +691,6 @@ mod tests { #[test] fn removal_of_old_voters_votes_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash = BlakeTwo256::hash_of(&proposal); let end = 4; @@ -720,7 +725,6 @@ mod tests { #[test] fn removal_of_old_voters_votes_works_with_set_members() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash = BlakeTwo256::hash_of(&proposal); let end = 4; @@ -755,7 +759,6 @@ mod tests { #[test] fn propose_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash = proposal.blake2_256().into(); let end = 4; @@ -769,7 +772,7 @@ mod tests { assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::Finalization, + phase: Phase::Initialization, event: Event::collective_Instance1(RawEvent::Proposed( 1, 0, @@ -785,7 +788,6 @@ mod tests { #[test] fn motions_ignoring_non_collective_proposals_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); assert_noop!( Collective::propose(Origin::signed(42), 3, Box::new(proposal.clone())), @@ -797,7 +799,6 @@ mod tests { #[test] fn motions_ignoring_non_collective_votes_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone()))); @@ -825,7 +826,6 @@ mod tests { #[test] fn motions_revoting_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash: H256 = proposal.blake2_256().into(); let end = 4; @@ -850,7 +850,7 @@ mod tests { assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::Finalization, + phase: Phase::Initialization, event: Event::collective_Instance1(RawEvent::Proposed( 1, 0, @@ -860,7 +860,7 @@ mod tests { topics: vec![], }, EventRecord { - phase: Phase::Finalization, + phase: Phase::Initialization, event: Event::collective_Instance1(RawEvent::Voted( 1, hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), @@ -877,7 +877,6 @@ mod tests { #[test] fn motions_reproposing_disapproved_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone()))); @@ -891,7 +890,6 @@ mod tests { #[test] fn motions_disapproval_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone()))); @@ -899,7 +897,7 @@ mod tests { assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::Finalization, + phase: Phase::Initialization, event: Event::collective_Instance1( RawEvent::Proposed( 1, @@ -910,7 +908,7 @@ mod tests { topics: vec![], }, EventRecord { - phase: Phase::Finalization, + phase: Phase::Initialization, event: Event::collective_Instance1(RawEvent::Voted( 2, hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), @@ -921,7 +919,7 @@ mod tests { topics: vec![], }, EventRecord { - phase: Phase::Finalization, + phase: Phase::Initialization, event: Event::collective_Instance1(RawEvent::Disapproved( hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), )), @@ -934,7 +932,6 @@ mod tests { #[test] fn motions_approval_works() { make_ext().execute_with(|| { - System::set_block_number(1); let proposal = make_proposal(42); let hash: H256 = proposal.blake2_256().into(); assert_ok!(Collective::propose(Origin::signed(1), 2, Box::new(proposal.clone()))); @@ -942,7 +939,7 @@ mod tests { assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::Finalization, + phase: Phase::Initialization, event: Event::collective_Instance1(RawEvent::Proposed( 1, 0, @@ -952,7 +949,7 @@ mod tests { topics: vec![], }, EventRecord { - phase: Phase::Finalization, + phase: Phase::Initialization, event: Event::collective_Instance1(RawEvent::Voted( 2, hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), @@ -963,14 +960,14 @@ mod tests { topics: vec![], }, EventRecord { - phase: Phase::Finalization, + phase: Phase::Initialization, event: Event::collective_Instance1(RawEvent::Approved( hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), )), topics: vec![], }, EventRecord { - phase: Phase::Finalization, + phase: Phase::Initialization, event: Event::collective_Instance1(RawEvent::Executed( hex!["68eea8f20b542ec656c6ac2d10435ae3bd1729efc34d1354ab85af840aad2d35"].into(), false, diff --git a/frame/contracts/COMPLEXITY.md b/frame/contracts/COMPLEXITY.md index 1d8a122aa0039d3c86c069a89842db4e722ca418..7e8c2903c79b9348d2441b46e181a0c59a6562d0 100644 --- a/frame/contracts/COMPLEXITY.md +++ b/frame/contracts/COMPLEXITY.md @@ -166,7 +166,8 @@ This function performs the following steps: 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 account will be deleted along with all its storage which requires time proportional to the number of storage entries of that account. +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. @@ -187,6 +188,23 @@ 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: @@ -350,6 +368,20 @@ 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. +## ext_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. + ## ext_return This function receives a `data` buffer as an argument. Execution of the function consists of the following steps: @@ -440,3 +472,27 @@ function performs a DB read. This function serializes the current block's number into the scratch buffer. **complexity**: Assuming that the block number is of constant size, this function has constant complexity. + +## 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. diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index 86f39f8a82b94d65e2da3e8535174d2ecde4ea77..a9318002cee2a71d290b56687f7123d5a1f994d7 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-contracts" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,25 +11,25 @@ description = "FRAME pallet for WASM contracts" [dependencies] serde = { version = "1.0.101", optional = true, features = ["derive"] } pwasm-utils = { version = "0.12.0", default-features = false } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } parity-wasm = { version = "0.41.0", default-features = false } wasmi-validation = { version = "0.3.0", default-features = false } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-sandbox = { version = "0.8.0-alpha.2", default-features = false, path = "../../primitives/sandbox" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -pallet-contracts-primitives = { version = "2.0.0-alpha.2", default-features = false, path = "common" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-sandbox = { version = "0.8.0-alpha.5", default-features = false, path = "../../primitives/sandbox" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +pallet-contracts-primitives = { version = "2.0.0-alpha.5", default-features = false, path = "common" } [dev-dependencies] wabt = "0.9.2" assert_matches = "1.3.0" hex-literal = "0.2.1" -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } -pallet-timestamp = { version = "2.0.0-alpha.2", path = "../timestamp" } -pallet-randomness-collective-flip = { version = "2.0.0-alpha.2", path = "../randomness-collective-flip" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } +pallet-timestamp = { version = "2.0.0-alpha.5", path = "../timestamp" } +pallet-randomness-collective-flip = { version = "2.0.0-alpha.5", path = "../randomness-collective-flip" } [features] default = ["std"] @@ -48,3 +48,6 @@ std = [ "wasmi-validation/std", "pallet-contracts-primitives/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/contracts/common/Cargo.toml b/frame/contracts/common/Cargo.toml index 0d009e7c82b41285fe12abdbe68ac07343c80985..d181896bd2a6c2bf4d25524e7fec277823f3cd02 100644 --- a/frame/contracts/common/Cargo.toml +++ b/frame/contracts/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-contracts-primitives" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,9 +10,9 @@ description = "A crate that hosts a common definitions that are relevant for the [dependencies] # This crate should not rely on any of the frame primitives. -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/runtime" } [features] default = ["std"] @@ -21,3 +21,6 @@ std = [ "sp-runtime/std", "sp-std/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/contracts/rpc/Cargo.toml b/frame/contracts/rpc/Cargo.toml index 092d049c5414ba7964860732fd8f8b3e2e3022a1..fa13d8ec3fe4a3fbca2f89750772a3f3a8f91251 100644 --- a/frame/contracts/rpc/Cargo.toml +++ b/frame/contracts/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-contracts-rpc" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,18 +9,21 @@ repository = "https://github.com/paritytech/substrate/" description = "Node-specific RPC methods for interaction with contracts." [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0" } +codec = { package = "parity-scale-codec", version = "1.3.0" } jsonrpc-core = "14.0.3" -jsonrpc-core-client = "14.0.3" +jsonrpc-core-client = "14.0.5" jsonrpc-derive = "14.0.3" -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-rpc = { version = "2.0.0-alpha.2", path = "../../../primitives/rpc" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-rpc = { version = "2.0.0-alpha.5", path = "../../../primitives/rpc" } serde = { version = "1.0.101", features = ["derive"] } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-api = { version = "2.0.0-alpha.2", path = "../../../primitives/api" } -pallet-contracts-primitives = { version = "2.0.0-alpha.2", path = "../common" } -pallet-contracts-rpc-runtime-api = { version = "0.8.0-alpha.2", path = "./runtime-api" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../primitives/api" } +pallet-contracts-primitives = { version = "2.0.0-alpha.5", path = "../common" } +pallet-contracts-rpc-runtime-api = { version = "0.8.0-alpha.5", path = "./runtime-api" } [dev-dependencies] serde_json = "1.0.41" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/contracts/rpc/runtime-api/Cargo.toml b/frame/contracts/rpc/runtime-api/Cargo.toml index 8435a6c4238c9e07d60c79a49aed9ae282993a76..692bd3f25ef85b3c695240897cc55bb0546eddcd 100644 --- a/frame/contracts/rpc/runtime-api/Cargo.toml +++ b/frame/contracts/rpc/runtime-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-contracts-rpc-runtime-api" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,11 +9,11 @@ repository = "https://github.com/paritytech/substrate/" description = "Runtime API definition required by Contracts RPC extensions." [dependencies] -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../../../primitives/api" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../../../primitives/runtime" } -pallet-contracts-primitives = { version = "2.0.0-alpha.2", default-features = false, path = "../../common" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../../../primitives/api" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../../../primitives/runtime" } +pallet-contracts-primitives = { version = "2.0.0-alpha.5", default-features = false, path = "../../common" } [features] default = ["std"] @@ -24,3 +24,6 @@ std = [ "sp-runtime/std", "pallet-contracts-primitives/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/contracts/src/account_db.rs b/frame/contracts/src/account_db.rs index 374c55c374d7bf26c0603017f3838b9c0a6a0bb7..165581e67646df41afa60945e5baefd6e62feb30 100644 --- a/frame/contracts/src/account_db.rs +++ b/frame/contracts/src/account_db.rs @@ -146,16 +146,8 @@ impl AccountDb for DirectAccountDb { let mut total_imbalance = SignedImbalance::zero(); for (address, changed) in s.into_iter() { if let Some(balance) = changed.balance() { - let existed = !T::Currency::total_balance(&address).is_zero(); let imbalance = T::Currency::make_free_balance_be(&address, balance); - let exists = !T::Currency::total_balance(&address).is_zero(); total_imbalance = total_imbalance.merge(imbalance); - if existed && !exists { - // Account killed. This will ultimately lead to calling `OnKilledAccount` callback - // which will make removal of CodeHashOf and AccountStorage for this account. - // In order to avoid writing over the deleted properties we `continue` here. - continue; - } } if changed.code_hash().is_some() diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 19d1d52497f231399497dd608a39800c0bb0d059..402622331d0ecc33ae66a8920b0568aaad099dd2 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -128,6 +128,13 @@ pub trait Ext { gas_meter: &mut GasMeter, ) -> Result<(), DispatchError>; + /// Transfer all funds to `beneficiary` and delete the contract. + fn terminate( + &mut self, + beneficiary: &AccountIdOf, + gas_meter: &mut GasMeter, + ) -> Result<(), DispatchError>; + /// Call (possibly transferring some amount of funds) into the specified account. fn call( &mut self, @@ -282,7 +289,7 @@ pub enum DeferredAction { } pub struct ExecutionContext<'a, T: Trait + 'a, V, L> { - pub parent: Option<&'a ExecutionContext<'a, T, V, L>>, + pub caller: Option<&'a ExecutionContext<'a, T, V, L>>, pub self_account: T::AccountId, pub self_trie_id: Option, pub overlay: OverlayAccountDb<'a, T>, @@ -307,7 +314,7 @@ where /// account (not a contract). pub fn top_level(origin: T::AccountId, cfg: &'a Config, vm: &'a V, loader: &'a L) -> Self { ExecutionContext { - parent: None, + caller: None, self_trie_id: None, self_account: origin, overlay: OverlayAccountDb::::new(&DirectAccountDb), @@ -325,7 +332,7 @@ where -> ExecutionContext<'b, T, V, L> { ExecutionContext { - parent: Some(self), + caller: Some(self), self_trie_id: trie_id, self_account: dest, overlay: OverlayAccountDb::new(&self.overlay), @@ -428,20 +435,6 @@ where gas_meter, )?; - // Destroy contract if insufficient remaining balance. - if nested.overlay.get_balance(&dest) < nested.config.existential_deposit { - let parent = nested.parent - .expect("a nested execution context must have a parent; qed"); - if parent.is_live(&dest) { - return Err(ExecError { - reason: "contract cannot be destroyed during recursive execution".into(), - buffer: output.data, - }); - } - - nested.overlay.destroy_contract(&dest); - } - Ok(output) } None => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }), @@ -535,9 +528,37 @@ where Ok((dest, output)) } - fn new_call_context<'b>(&'b mut self, caller: T::AccountId, value: BalanceOf) - -> CallContext<'b, 'a, T, V, L> - { + pub fn terminate( + &mut self, + beneficiary: &T::AccountId, + gas_meter: &mut GasMeter, + ) -> Result<(), DispatchError> { + let self_id = self.self_account.clone(); + let value = self.overlay.get_balance(&self_id); + if let Some(caller) = self.caller { + if caller.is_live(&self_id) { + return Err(DispatchError::Other( + "Cannot terminate a contract that is present on the call stack", + )); + } + } + transfer( + gas_meter, + TransferCause::Terminate, + &self_id, + beneficiary, + value, + self, + )?; + self.overlay.destroy_contract(&self_id); + Ok(()) + } + + fn new_call_context<'b>( + &'b mut self, + caller: T::AccountId, + value: BalanceOf, + ) -> CallContext<'b, 'a, T, V, L> { let timestamp = self.timestamp.clone(); let block_number = self.block_number.clone(); CallContext { @@ -571,7 +592,7 @@ where /// stack, meaning it is in the middle of an execution. fn is_live(&self, account: &T::AccountId) -> bool { &self.self_account == account || - self.parent.map_or(false, |parent| parent.is_live(account)) + self.caller.map_or(false, |caller| caller.is_live(account)) } } @@ -606,6 +627,7 @@ impl Token for TransferFeeToken> { enum TransferCause { Call, Instantiate, + Terminate, } /// Transfer some funds from `transactor` to `dest`. @@ -642,7 +664,7 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( Instantiate => ContractInstantiate, // Otherwise the fee is to transfer to an account. - Call => TransferFeeKind::Transfer, + Call | Terminate => TransferFeeKind::Transfer, }; TransferFeeToken { kind, @@ -664,11 +686,19 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( if to_balance.is_zero() && value < ctx.config.existential_deposit { Err("value too low to create account")? } + + // Only ext_terminate is allowed to bring the sender below the existential deposit + let required_balance = match cause { + Terminate => 0.into(), + _ => ctx.config.existential_deposit + }; + T::Currency::ensure_can_withdraw( transactor, value, WithdrawReason::Transfer.into(), - new_from_balance, + new_from_balance.checked_sub(&required_balance) + .ok_or("brings sender below existential deposit")?, )?; let new_to_balance = match to_balance.checked_add(&value) { @@ -740,6 +770,14 @@ where self.ctx.transfer(to.clone(), value, gas_meter) } + fn terminate( + &mut self, + beneficiary: &AccountIdOf, + gas_meter: &mut GasMeter, + ) -> Result<(), DispatchError> { + self.ctx.terminate(beneficiary, gas_meter) + } + fn call( &mut self, to: &T::AccountId, @@ -1321,7 +1359,7 @@ mod tests { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); - ctx.overlay.set_balance(&ALICE, 1); + ctx.overlay.set_balance(&ALICE, 100); let result = ctx.instantiate( 1, @@ -1591,6 +1629,7 @@ mod tests { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); ctx.overlay.set_balance(&ALICE, 1000); + ctx.overlay.set_balance(&BOB, 100); ctx.overlay.instantiate_contract(&BOB, instantiator_ch).unwrap(); assert_matches!( @@ -1650,6 +1689,7 @@ mod tests { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); ctx.overlay.set_balance(&ALICE, 1000); + ctx.overlay.set_balance(&BOB, 100); ctx.overlay.instantiate_contract(&BOB, instantiator_ch).unwrap(); assert_matches!( @@ -1668,6 +1708,45 @@ mod tests { }); } + #[test] + fn termination_from_instantiate_fails() { + let vm = MockVm::new(); + + let mut loader = MockLoader::empty(); + + let terminate_ch = loader.insert(|mut ctx| { + ctx.ext.terminate(&ALICE, &mut ctx.gas_meter).unwrap(); + exec_success() + }); + + ExtBuilder::default() + .existential_deposit(15) + .build() + .execute_with(|| { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + ctx.overlay.set_balance(&ALICE, 1000); + + assert_matches!( + ctx.instantiate( + 100, + &mut GasMeter::::with_limit(10000, 1), + &terminate_ch, + vec![], + ), + Err(ExecError { + reason: DispatchError::Other("insufficient remaining balance"), + buffer + }) if buffer == Vec::::new() + ); + + assert_eq!( + &ctx.events(), + &[] + ); + }); + } + #[test] fn rent_allowance() { let vm = MockVm::new(); @@ -1683,7 +1762,7 @@ mod tests { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); - ctx.overlay.set_balance(&ALICE, 1); + ctx.overlay.set_balance(&ALICE, 100); let result = ctx.instantiate( 1, diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index eeaa35a53d278177156136df75057204f699a9bd..090b345099e916a827382adbd5903bb4cecfeb6c 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -125,7 +125,7 @@ use frame_support::{ parameter_types, IsSubType, weights::DispatchInfo, }; -use frame_support::traits::{OnKilledAccount, OnUnbalanced, Currency, Get, Time, Randomness}; +use frame_support::traits::{OnUnbalanced, Currency, Get, Time, Randomness}; use frame_system::{self as system, ensure_signed, RawOrigin, ensure_root}; use sp_core::storage::well_known_keys::CHILD_STORAGE_KEY_PREFIX; use pallet_contracts_primitives::{RentProjection, ContractAccessError}; @@ -548,6 +548,7 @@ decl_module! { /// Updates the schedule for metering contracts. /// /// The schedule must have a greater version than the stored schedule. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn update_schedule(origin, schedule: Schedule) -> DispatchResult { ensure_root(origin)?; if >::current_schedule().version >= schedule.version { @@ -562,6 +563,7 @@ decl_module! { /// Stores the given binary Wasm code into the chain's storage and returns its `codehash`. /// You can instantiate contracts only with stored code. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn put_code( origin, #[compact] gas_limit: Gas, @@ -589,6 +591,7 @@ decl_module! { /// * 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. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn call( origin, dest: ::Source, @@ -614,6 +617,7 @@ decl_module! { /// after the execution is saved as the `code` of the account. That code will be invoked /// upon any call received by this account. /// - The contract is initialized. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn instantiate( origin, #[compact] endowment: BalanceOf, @@ -636,6 +640,7 @@ decl_module! { /// /// If contract is not evicted as a result of this call, no actions are taken and /// the sender is not eligible for the reward. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option) { let origin = origin.into(); let (signed, rewarded) = match (origin, aux_sender) { @@ -941,18 +946,6 @@ decl_storage! { } } -// TODO: this should be removed in favour of a self-destruct contract host function allowing the -// contract to delete all storage and the `ContractInfoOf` key and transfer remaining balance to -// some other account. As it stands, it's an economic insecurity on any smart-contract chain. -// https://github.com/paritytech/substrate/issues/4952 -impl OnKilledAccount for Module { - fn on_killed_account(who: &T::AccountId) { - if let Some(ContractInfo::Alive(info)) = >::take(who) { - child::kill_storage(&info.trie_id, info.child_trie_unique_id()); - } - } -} - /// In-memory cache of configuration values. /// /// We assume that these values can't be changed in the diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index f6b9e0e57e410f6ac6aff559d6506ced984cebcc..04b9b1ee4ca7775ebb53d434bbcc64cd465cef49 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -116,8 +116,8 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); - type OnKilledAccount = Contracts; + type OnNewAccount = (); + type OnKilledAccount = (); } impl pallet_balances::Trait for Test { type Balance = u64; @@ -279,7 +279,9 @@ impl ExtBuilder { }, gas_price: self.gas_price, }.assimilate_storage(&mut t).unwrap(); - sp_io::TestExternalities::new(t) + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext } } @@ -308,7 +310,7 @@ fn refunds_unused_gas() { } #[test] -fn account_removal_removes_storage() { +fn account_removal_does_not_remove_storage() { ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let trie_id1 = ::TrieIdGenerator::trie_id(&1); let trie_id2 = ::TrieIdGenerator::trie_id(&2); @@ -351,14 +353,22 @@ fn account_removal_removes_storage() { // Transfer funds from account 1 of such amount that after this transfer // the balance of account 1 will be below the existential threshold. // - // This should lead to the removal of all storage associated with this account. + // 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 `ext_terminate`. There is no external event that can remove + // the account appart from that. assert_ok!(Balances::transfer(Origin::signed(1), 2, 20)); - // Verify that all entries from account 1 is removed, while - // entries from account 2 is in place. + // Verify that no entries are removed. { - assert!(>::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key1).is_none()); - assert!(>::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key2).is_none()); + assert_eq!( + >::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key1), + Some(b"1".to_vec()) + ); + assert_eq!( + >::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key2), + Some(b"2".to_vec()) + ); assert_eq!( >::get_storage(&DirectAccountDb, &2, Some(&trie_id2), key1), @@ -422,44 +432,44 @@ fn instantiate_and_call_and_deposit_event() { assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(BOB)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances( pallet_balances::RawEvent::Endowed(BOB, 100) ), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::ContractExecution(BOB, vec![1, 2, 3, 4])), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, BOB)), topics: vec![], } @@ -505,17 +515,17 @@ fn dispatch_call() { // wasm source this test will fail and will show you the actual hash. assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())), topics: vec![], }, @@ -539,58 +549,58 @@ fn dispatch_call() { assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(BOB)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances( pallet_balances::RawEvent::Endowed(BOB, 100) ), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, BOB)), topics: vec![], }, // Dispatching the call. EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(CHARLIE)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances( pallet_balances::RawEvent::Endowed(CHARLIE, 50) ), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances( pallet_balances::RawEvent::Transfer(BOB, CHARLIE, 50) ), @@ -599,7 +609,7 @@ fn dispatch_call() { // Event emitted as a result of dispatch. EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Dispatched(BOB, true)), topics: vec![], } @@ -643,17 +653,17 @@ fn dispatch_call_not_dispatched_after_top_level_transaction_failure() { // wasm source this test will fail and will show you the actual hash. assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())), topics: vec![], }, @@ -681,39 +691,39 @@ fn dispatch_call_not_dispatched_after_top_level_transaction_failure() { ); assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(BOB)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances( pallet_balances::RawEvent::Endowed(BOB, 100) ), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, BOB)), topics: vec![], }, @@ -898,17 +908,17 @@ fn test_set_rent_code_and_hash() { // and will show you the actual hash. assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())), topics: vec![], }, @@ -1225,7 +1235,7 @@ fn call_removed_contract() { // Calling a contract that is about to evict shall emit an event. assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Evicted(BOB, true)), topics: vec![], }, @@ -1414,22 +1424,22 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: // and will show you the actual hash. assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::CodeStored(restoration_code_hash.into())), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::CodeStored(set_rent_code_hash.into())), topics: vec![], }, @@ -1473,7 +1483,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts( RawEvent::Evicted(BOB.clone(), true) ), @@ -1526,7 +1536,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: (true, false) => { assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts( RawEvent::Restored(DJANGO, BOB, bob_code_hash, 50, false) ), @@ -1537,42 +1547,42 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: (_, true) => { assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Evicted(BOB, true)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(CHARLIE)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(CHARLIE, 1_000_000)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(DJANGO)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(DJANGO, 30_000)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Transfer(CHARLIE, DJANGO, 30_000)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Instantiated(CHARLIE, DJANGO)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Restored( DJANGO, BOB, @@ -1599,12 +1609,12 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: assert!(ContractInfoOf::::get(DJANGO).is_none()); assert_eq!(System::events(), vec![ EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::system(system::RawEvent::KilledAccount(DJANGO)), topics: vec![], }, EventRecord { - phase: Phase::ApplyExtrinsic(0), + phase: Phase::Initialization, event: MetaEvent::contracts( RawEvent::Restored(DJANGO, BOB, bob_contract.code_hash, 50, true) ), @@ -2088,13 +2098,107 @@ fn deploy_works_without_gas_price() { }); } +const CODE_DRAIN: &str = r#" +(module + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_balance" (func $ext_balance)) + (import "env" "ext_call" (func $ext_call (param i32 i32 i64 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") + ;; Send entire remaining balance to the 0 address. + (call $ext_balance) + + ;; Balance should be encoded as a u64. + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 8) + ) + ) + + ;; Read balance into memory. + (call $ext_scratch_read + (i32.const 8) ;; Pointer to write balance to + (i32.const 0) ;; Offset into scratch buffer + (i32.const 8) ;; Length of encoded balance + ) + + ;; Self-destruct by sending full balance to the 0 address. + (call $assert + (i32.eq + (call $ext_call + (i32.const 0) ;; Pointer to destination address + (i32.const 8) ;; Length of destination address + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 8) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer + ) + (i32.const 0) + ) + ) + ) +) +"#; + +#[test] +fn cannot_self_destruct_through_draning() { + let (wasm, code_hash) = compile_module::(CODE_DRAIN).unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // Instantiate the BOB contract. + assert_ok!(Contracts::instantiate( + Origin::signed(ALICE), + 100_000, + 100_000, + code_hash.into(), + vec![], + )); + + // Check that the BOB contract has been instantiated. + assert_matches!( + ContractInfoOf::::get(BOB), + Some(ContractInfo::Alive(_)) + ); + + // Call BOB with no input data, forcing it to run until out-of-balance + // and eventually trapping because below existential deposit. + assert_err!( + Contracts::call( + Origin::signed(ALICE), + BOB, + 0, + 100_000, + vec![], + ), + "contract trapped during execution" + ); + }); +} + const CODE_SELF_DESTRUCT: &str = r#" (module (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) (import "env" "ext_address" (func $ext_address)) - (import "env" "ext_balance" (func $ext_balance)) (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) + (import "env" "ext_terminate" (func $ext_terminate (param i32 i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -2148,47 +2252,23 @@ const CODE_SELF_DESTRUCT: &str = r#" ) ) ) - ) - - ;; Send entire remaining balance to the 0 address. - (call $ext_balance) - - ;; Balance should be encoded as a u64. - (call $assert - (i32.eq - (call $ext_scratch_size) - (i32.const 8) - ) - ) - - ;; Read balance into memory. - (call $ext_scratch_read - (i32.const 8) ;; Pointer to write balance to - (i32.const 0) ;; Offset into scratch buffer - (i32.const 8) ;; Length of encoded balance - ) - - ;; Self-destruct by sending full balance to the 0 address. - (call $assert - (i32.eq - (call $ext_call - (i32.const 0) ;; Pointer to destination address - (i32.const 8) ;; Length of destination address - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. - (i32.const 8) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer - (i32.const 0) ;; Pointer to input data buffer address - (i32.const 0) ;; Length of input data buffer + (else + ;; Try to terminate and give balance to django. + (call $ext_terminate + (i32.const 32) ;; Pointer to beneficiary address + (i32.const 8) ;; Length of beneficiary address ) - (i32.const 0) + (unreachable) ;; ext_terminate never returns ) ) ) + ;; Address of django + (data (i32.const 32) "\04\00\00\00\00\00\00\00") ) "#; #[test] -fn self_destruct_by_draining_balance() { +fn cannot_self_destruct_while_live() { let (wasm, code_hash) = compile_module::(CODE_SELF_DESTRUCT).unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { Balances::deposit_creating(&ALICE, 1_000_000); @@ -2209,22 +2289,29 @@ fn self_destruct_by_draining_balance() { Some(ContractInfo::Alive(_)) ); - // Call BOB with no input data, forcing it to self-destruct. - assert_ok!(Contracts::call( - Origin::signed(ALICE), - BOB, - 0, - 100_000, - vec![], - )); + // Call BOB with input data, forcing it make a recursive call to itself to + // self-destruct, resulting in a trap. + assert_err!( + Contracts::call( + Origin::signed(ALICE), + BOB, + 0, + 100_000, + vec![0], + ), + "contract trapped during execution" + ); - // Check that BOB is now dead. - assert!(ContractInfoOf::::get(BOB).is_none()); + // Check that BOB is still alive. + assert_matches!( + ContractInfoOf::::get(BOB), + Some(ContractInfo::Alive(_)) + ); }); } #[test] -fn cannot_self_destruct_while_live() { +fn self_destruct_works() { let (wasm, code_hash) = compile_module::(CODE_SELF_DESTRUCT).unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { Balances::deposit_creating(&ALICE, 1_000_000); @@ -2245,24 +2332,23 @@ fn cannot_self_destruct_while_live() { Some(ContractInfo::Alive(_)) ); - // Call BOB with input data, forcing it make a recursive call to itself to - // self-destruct, resulting in a trap. - assert_err!( + // Call BOB without input data which triggers termination. + assert_matches!( Contracts::call( Origin::signed(ALICE), BOB, 0, 100_000, - vec![0], + vec![], ), - "contract trapped during execution" + Ok(()) ); - // Check that BOB is still alive. - assert_matches!( - ContractInfoOf::::get(BOB), - Some(ContractInfo::Alive(_)) - ); + // Check that account is gone + assert!(ContractInfoOf::::get(BOB).is_none()); + + // check that the beneficiary (django) got remaining balance + assert_eq!(Balances::free_balance(DJANGO), 100_000); }); } @@ -2524,8 +2610,8 @@ fn cannot_self_destruct_in_constructor() { Balances::deposit_creating(&ALICE, 1_000_000); assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm)); - // Fail to instantiate the BOB contract since its final balance is below existential - // deposit. + // Fail to instantiate the BOB because the call that is issued in the deploy + // function exhausts all balances which puts it below the existential deposit. assert_err!( Contracts::instantiate( Origin::signed(ALICE), @@ -2534,7 +2620,7 @@ fn cannot_self_destruct_in_constructor() { code_hash.into(), vec![], ), - "insufficient remaining balance" + "contract trapped during execution" ); }); } @@ -2660,3 +2746,137 @@ fn get_runtime_storage() { )); }); } + +const CODE_CRYPTO_HASHES: &str = r#" +(module + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32))) + + (import "env" "ext_hash_sha2_256" (func $ext_hash_sha2_256 (param i32 i32 i32))) + (import "env" "ext_hash_keccak_256" (func $ext_hash_keccak_256 (param i32 i32 i32))) + (import "env" "ext_hash_blake2_256" (func $ext_hash_blake2_256 (param i32 i32 i32))) + (import "env" "ext_hash_blake2_128" (func $ext_hash_blake2_128 (param i32 i32 i32))) + + (import "env" "memory" (memory 1 1)) + + (type $hash_fn_sig (func (param i32 i32 i32))) + (table 8 funcref) + (elem (i32.const 1) + $ext_hash_sha2_256 + $ext_hash_keccak_256 + $ext_hash_blake2_256 + $ext_hash_blake2_128 + ) + (data (i32.const 1) "20202010201008") ;; Output sizes of the hashes in order in hex. + + ;; Not in use by the tests besides instantiating the contract. + (func (export "deploy")) + + ;; Called by the tests. + ;; + ;; The `call` function expects data in a certain format in the scratch + ;; buffer. + ;; + ;; 1. The first byte encodes an identifier for the crypto hash function + ;; under test. (*) + ;; 2. The rest encodes the input data that is directly fed into the + ;; crypto hash function chosen in 1. + ;; + ;; The `deploy` function then computes the chosen crypto hash function + ;; given the input and puts the result back into the scratch buffer. + ;; After contract execution the test driver then asserts that the returned + ;; values are equal to the expected bytes for the input and chosen hash + ;; function. + ;; + ;; (*) The possible value for the crypto hash identifiers can be found below: + ;; + ;; | value | Algorithm | Bit Width | + ;; |-------|-----------|-----------| + ;; | 0 | SHA2 | 256 | + ;; | 1 | KECCAK | 256 | + ;; | 2 | BLAKE2 | 256 | + ;; | 3 | BLAKE2 | 128 | + ;; --------------------------------- + (func (export "call") (result i32) + (local $chosen_hash_fn i32) + (local $input_ptr i32) + (local $input_len i32) + (local $output_ptr i32) + (local $output_len i32) + (local.set $input_ptr (i32.const 10)) + (call $ext_scratch_read (local.get $input_ptr) (i32.const 0) (call $ext_scratch_size)) + (local.set $chosen_hash_fn (i32.load8_u (local.get $input_ptr))) + (if (i32.gt_u (local.get $chosen_hash_fn) (i32.const 7)) + ;; We check that the chosen hash fn identifier is within bounds: [0,7] + (unreachable) + ) + (local.set $input_ptr (i32.add (local.get $input_ptr) (i32.const 1))) + (local.set $input_len (i32.sub (call $ext_scratch_size) (i32.const 1))) + (local.set $output_ptr (i32.const 100)) + (local.set $output_len (i32.load8_u (local.get $chosen_hash_fn))) + (call_indirect (type $hash_fn_sig) + (local.get $input_ptr) + (local.get $input_len) + (local.get $output_ptr) + (local.get $chosen_hash_fn) ;; Which crypto hash function to execute. + ) + (call $ext_scratch_write + (local.get $output_ptr) ;; Linear memory location of the output buffer. + (local.get $output_len) ;; Number of output buffer bytes. + ) + (i32.const 0) + ) +) +"#; + +#[test] +fn crypto_hashes() { + let (wasm, code_hash) = compile_module::(&CODE_CRYPTO_HASHES).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // Instantiate the CRYPTO_HASHES contract. + assert_ok!(Contracts::instantiate( + Origin::signed(ALICE), + 100_000, + 100_000, + code_hash.into(), + vec![], + )); + // Perform the call. + let input = b"_DEAD_BEEF"; + use sp_io::hashing::*; + // Wraps a hash function into a more dynamic form usable for testing. + macro_rules! dyn_hash_fn { + ($name:ident) => { + Box::new(|input| $name(input).as_ref().to_vec().into_boxed_slice()) + }; + } + // All hash functions and their associated output byte lengths. + let test_cases: &[(Box Box<[u8]>>, usize)] = &[ + (dyn_hash_fn!(sha2_256), 32), + (dyn_hash_fn!(keccak_256), 32), + (dyn_hash_fn!(blake2_256), 32), + (dyn_hash_fn!(blake2_128), 16), + ]; + // Test the given hash functions for the input: "_DEAD_BEEF" + for (n, (hash_fn, expected_size)) in test_cases.iter().enumerate() { + // 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, + BOB, + 0, + 100_000, + params, + ).unwrap(); + assert_eq!(result.status, 0); + let expected = hash_fn(input.as_ref()); + assert_eq!(&result.data[..*expected_size], &*expected); + } + }) +} diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 1b0401a5626e6d80ec18af492943f01dd5f9de6b..8911fb72b6130c66fa1cf7d88d6fcc86b062b5dd 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -182,6 +182,12 @@ mod tests { gas_left: u64, } + #[derive(Debug, PartialEq, Eq)] + struct TerminationEntry { + beneficiary: u64, + gas_left: u64, + } + #[derive(Debug, PartialEq, Eq)] struct TransferEntry { to: u64, @@ -195,6 +201,7 @@ mod tests { storage: HashMap>, rent_allowance: u64, instantiates: Vec, + terminations: Vec, transfers: Vec, dispatches: Vec, restores: Vec, @@ -242,7 +249,13 @@ mod tests { let address = self.next_account_id; self.next_account_id += 1; - Ok((address, ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() })) + Ok(( + address, + ExecReturnValue { + status: STATUS_SUCCESS, + data: Vec::new(), + }, + )) } fn transfer( &mut self, @@ -275,6 +288,17 @@ mod tests { // TODO: Add tests for different call outcomes. Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }) } + fn terminate( + &mut self, + beneficiary: &u64, + gas_meter: &mut GasMeter, + ) -> Result<(), DispatchError> { + self.terminations.push(TerminationEntry { + beneficiary: *beneficiary, + gas_left: gas_meter.gas_left(), + }); + Ok(()) + } fn note_dispatch_call(&mut self, call: Call) { self.dispatches.push(DispatchEntry(call)); } @@ -379,6 +403,13 @@ mod tests { ) -> Result<(), DispatchError> { (**self).transfer(to, value, gas_meter) } + fn terminate( + &mut self, + beneficiary: &u64, + gas_meter: &mut GasMeter, + ) -> Result<(), DispatchError> { + (**self).terminate(beneficiary, gas_meter) + } fn call( &mut self, to: &u64, @@ -649,6 +680,47 @@ mod tests { ); } + const CODE_TERMINATE: &str = r#" +(module + ;; ext_terminate( + ;; beneficiary_ptr: u32, + ;; beneficiary_len: u32, + ;; ) + (import "env" "ext_terminate" (func $ext_terminate (param i32 i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (call $ext_terminate + (i32.const 4) ;; Pointer to "beneficiary" address. + (i32.const 8) ;; Length of "beneficiary" address. + ) + ) + (func (export "deploy")) + + ;; Beneficiary AccountId to transfer the funds. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 4) "\09\00\00\00\00\00\00\00") +) +"#; + + #[test] + fn contract_terminate() { + let mut mock_ext = MockExt::default(); + execute( + CODE_TERMINATE, + vec![], + &mut mock_ext, + &mut GasMeter::with_limit(50_000, 1), + ).unwrap(); + + assert_eq!( + &mock_ext.terminations, + &[TerminationEntry { + beneficiary: 0x09, + gas_left: 49989, + }] + ); + } + const CODE_TRANSFER_LIMITED_GAS: &str = r#" (module ;; ext_call( diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 24609446873140a1d5dec4b29ec7bdf712a9e557..7cede5542fc6f8bde389f6b99fc960b7b3512e24 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -26,6 +26,12 @@ use frame_system; use sp_std::{prelude::*, mem, convert::TryInto}; use codec::{Decode, Encode}; use sp_runtime::traits::{Bounded, SaturatedConversion}; +use sp_io::hashing::{ + keccak_256, + blake2_256, + blake2_128, + sha2_256, +}; /// The value returned from ext_call and ext_instantiate contract external functions if the call or /// instantiation traps. This value is chosen as if the execution does not trap, the return value @@ -41,6 +47,9 @@ enum SpecialTrap { Return(Vec), /// Signals that trap was generated because the contract exhausted its gas limit. OutOfGas, + /// Signals that a trap was generated in response to a succesful call to the + /// `ext_terminate` host function. + Termination, } /// Can only be used for one call. @@ -83,14 +92,20 @@ pub(crate) fn to_execution_result( status: STATUS_SUCCESS, data, }) - } + }, + Some(SpecialTrap::Termination) => { + return Ok(ExecReturnValue { + status: STATUS_SUCCESS, + data: Vec::new(), + }) + }, Some(SpecialTrap::OutOfGas) => { return Err(ExecError { reason: "ran out of gas during contract execution".into(), buffer: runtime.scratch_buf, }) - } - _ => (), + }, + None => (), } // Check the exact type of the error. @@ -443,6 +458,9 @@ define_env!(Env, , // changes made by the called contract are reverted. The scratch buffer is filled with the // output data returned by the called contract, even in the case of a failure status. // + // This call fails if it would bring the calling contract below the existential deposit. + // In order to destroy a contract `ext_terminate` must be used. + // // If the contract traps during execution or otherwise fails to complete successfully, then // this function clears the scratch buffer and returns 0x0100. As with a failure status, any // state changes made by the called contract are reverted. @@ -523,6 +541,9 @@ define_env!(Env, , // of the newly instantiated contract. In the case of a failure status, the scratch buffer is // cleared. // + // This call fails if it would bring the calling contract below the existential deposit. + // In order to destroy a contract `ext_terminate` must be used. + // // If the contract traps during execution or otherwise fails to complete successfully, then // this function clears the scratch buffer and returns 0x0100. As with a failure status, any // state changes made by the called contract are reverted. @@ -601,6 +622,30 @@ define_env!(Env, , } }, + // 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 transfered. + // Should be decodable as an `T::AccountId`. Traps otherwise. + // - beneficiary_len: length of the address buffer. + ext_terminate( + ctx, + beneficiary_ptr: u32, + beneficiary_len: u32 + ) => { + let beneficiary: <::T as frame_system::Trait>::AccountId = + read_sandbox_memory_as(ctx, beneficiary_ptr, beneficiary_len)?; + + if let Ok(_) = ctx.ext.terminate(&beneficiary, ctx.gas_meter) { + ctx.special_trap = Some(SpecialTrap::Termination); + } + Err(sp_sandbox::HostError) + }, + // Save a data buffer as a result of the execution, terminate the execution and return a // successful result to the caller. // @@ -974,8 +1019,145 @@ define_env!(Env, , } } }, + + // 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. + ext_hash_sha2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + compute_hash_on_intermediate_buffer(ctx, 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. + ext_hash_keccak_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + compute_hash_on_intermediate_buffer(ctx, 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. + ext_hash_blake2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + compute_hash_on_intermediate_buffer(ctx, 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. + ext_hash_blake2_128(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + compute_hash_on_intermediate_buffer(ctx, blake2_128, input_ptr, input_len, output_ptr) + }, ); +/// Computes the given hash function on the scratch buffer. +/// +/// Reads from the sandboxed input buffer into an intermediate buffer. +/// Returns the result directly to the output buffer of the sandboxed memory. +/// +/// 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. +/// +/// # Note +/// +/// The `input` and `output` buffers may overlap. +fn compute_hash_on_intermediate_buffer( + ctx: &mut Runtime, + hash_fn: F, + input_ptr: u32, + input_len: u32, + output_ptr: u32, +) -> Result<(), sp_sandbox::HostError> +where + E: Ext, + F: FnOnce(&[u8]) -> R, + R: AsRef<[u8]>, +{ + // Copy the input buffer directly into the scratch buffer to avoid + // heap allocations. + let input = read_sandbox_memory(ctx, input_ptr, input_len)?; + // Compute the hash on the scratch buffer using the given hash function. + let hash = hash_fn(&input); + // Write the resulting hash back into the sandboxed output buffer. + write_sandbox_memory( + ctx.schedule, + &mut ctx.special_trap, + ctx.gas_meter, + &ctx.memory, + output_ptr, + hash.as_ref(), + )?; + Ok(()) +} + /// Finds duplicates in a given vector. /// /// This function has complexity of O(n log n) and no additional memory is required, although diff --git a/frame/democracy/Cargo.toml b/frame/democracy/Cargo.toml index d2938956bd015333e28c7bbd67fc4a61a1bea282..888e23e01b21e3f581bc85263af003872f582545 100644 --- a/frame/democracy/Cargo.toml +++ b/frame/democracy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-democracy" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,17 +10,18 @@ description = "FRAME pallet for democracy" [dependencies] serde = { version = "1.0.101", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } -sp-storage = { version = "2.0.0-alpha.2", path = "../../primitives/storage" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } +sp-storage = { version = "2.0.0-alpha.5", path = "../../primitives/storage" } hex-literal = "0.2.1" [features] @@ -30,7 +31,16 @@ std = [ "codec/std", "sp-std/std", "sp-io/std", + "frame-benchmarking/std", "frame-support/std", "sp-runtime/std", "frame-system/std", ] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..2429edbefd8d1b0a7bcfe54d6f9d442fea45299b --- /dev/null +++ b/frame/democracy/src/benchmarking.rs @@ -0,0 +1,456 @@ +// Copyright 2020 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 . + +//! Democracy pallet benchmarking. + +use super::*; + +use frame_benchmarking::{benchmarks, account}; +use frame_support::traits::{Currency, Get}; +use frame_system::{RawOrigin, Module as System, self}; +use sp_runtime::traits::{Bounded, EnsureOrigin, One}; + +use crate::Module as Democracy; + +const SEED: u32 = 0; +const MAX_USERS: u32 = 1000; +const MAX_REFERENDUMS: u32 = 100; +const MAX_PROPOSALS: u32 = 100; + +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 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) +} + +fn add_referendum(n: u32) -> Result { + let proposal_hash = add_proposal::(n)?; + let vote_threshold = VoteThreshold::SimpleMajority; + + Democracy::::inject_referendum( + 0.into(), + proposal_hash, + vote_threshold, + 0.into(), + ); + let referendum_index: ReferendumIndex = ReferendumCount::get() - 1; + Ok(referendum_index) +} + +fn account_vote() -> AccountVote> { + let v = Vote { + aye: true, + conviction: Conviction::Locked1x, + }; + + AccountVote::Standard { + vote: v, + balance: BalanceOf::::one(), + } +} + +fn open_activate_proxy(u: u32) -> Result { + let caller = funded_account::("caller", u); + let proxy = funded_account::("proxy", u); + + Democracy::::open_proxy(RawOrigin::Signed(proxy.clone()).into(), caller.clone())?; + Democracy::::activate_proxy(RawOrigin::Signed(caller).into(), proxy.clone())?; + + Ok(proxy) +} + +benchmarks! { + _ { } + + propose { + let p in 1 .. MAX_PROPOSALS; + + // Add p proposals + for i in 0..p { + add_proposal::(i)?; + } + + let caller = funded_account::("caller", 0); + let proposal_hash: T::Hash = T::Hashing::hash_of(&p); + let value = T::MinimumDeposit::get(); + }: _(RawOrigin::Signed(caller), proposal_hash, value.into()) + + second { + let s in 0 .. 100; + + // Create s existing "seconds" + for i in 0..s { + let seconder = funded_account::("seconder", i); + Democracy::::second(RawOrigin::Signed(seconder).into(), 0)?; + } + + let caller = funded_account::("caller", 0); + let proposal_hash = add_proposal::(s)?; + }: _(RawOrigin::Signed(caller), 0) + + vote { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", 0); + let account_vote = account_vote::(); + + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + + let referendum_index = r - 1; + + }: _(RawOrigin::Signed(caller), referendum_index, account_vote) + + proxy_vote { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", r); + let proxy = open_activate_proxy::(r)?; + let account_vote = account_vote::(); + + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + + let referendum_index = r - 1; + + }: _(RawOrigin::Signed(proxy), referendum_index, account_vote) + + emergency_cancel { + let u in 1 .. MAX_USERS; + + let referendum_index = add_referendum::(u)?; + let origin = T::CancellationOrigin::successful_origin(); + let call = Call::::emergency_cancel(referendum_index); + }: { + let _ = call.dispatch(origin)?; + } + + external_propose { + let u in 1 .. MAX_USERS; + + let origin = T::ExternalOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&u); + let call = Call::::external_propose(proposal_hash); + }: { + let _ = call.dispatch(origin)?; + } + + external_propose_majority { + let u in 1 .. MAX_USERS; + + let origin = T::ExternalMajorityOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&u); + let call = Call::::external_propose_majority(proposal_hash); + + }: { + let _ = call.dispatch(origin)?; + } + + external_propose_default { + let u in 1 .. MAX_USERS; + + let origin = T::ExternalDefaultOrigin::successful_origin(); + let proposal_hash = T::Hashing::hash_of(&u); + let call = Call::::external_propose_default(proposal_hash); + + }: { + let _ = call.dispatch(origin)?; + } + + fast_track { + let u in 1 .. MAX_USERS; + + let origin_propose = T::ExternalDefaultOrigin::successful_origin(); + let proposal_hash: T::Hash = T::Hashing::hash_of(&u); + Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; + + let origin_fast_track = T::FastTrackOrigin::successful_origin(); + let voting_period = T::FastTrackVotingPeriod::get(); + let delay = 0; + let call = Call::::fast_track(proposal_hash, voting_period.into(), delay.into()); + + }: { + let _ = call.dispatch(origin_fast_track)?; + } + + veto_external { + // Existing veto-ers + let v in 0 .. 100; + + let proposal_hash: T::Hash = T::Hashing::hash_of(&v); + + let origin_propose = T::ExternalDefaultOrigin::successful_origin(); + Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; + + let mut vetoers: Vec = Vec::new(); + for i in 0..v { + vetoers.push(account("vetoer", i, SEED)); + } + Blacklist::::insert(proposal_hash, (T::BlockNumber::zero(), vetoers)); + + let call = Call::::veto_external(proposal_hash); + let origin = T::VetoOrigin::successful_origin(); + }: { + let _ = call.dispatch(origin)?; + } + + cancel_referendum { + let u in 1 .. MAX_USERS; + + let referendum_index = add_referendum::(u)?; + }: _(RawOrigin::Root, referendum_index) + + cancel_queued { + let d in 0 .. 100; + + let referendum_index = add_referendum::(d)?; + let block_number: T::BlockNumber = 0.into(); + let hash: T::Hash = T::Hashing::hash_of(&d); + >::put(vec![(block_number, hash, referendum_index.clone()); d as usize]); + }: _(RawOrigin::Root, referendum_index) + + open_proxy { + let u in 1 .. MAX_USERS; + + let caller: T::AccountId = funded_account::("caller", u); + let proxy: T::AccountId = funded_account::("proxy", u); + + }: _(RawOrigin::Signed(proxy), caller) + + activate_proxy { + let u in 1 .. MAX_USERS; + + let caller: T::AccountId = funded_account::("caller", u); + let proxy: T::AccountId = funded_account::("proxy", u); + Democracy::::open_proxy(RawOrigin::Signed(proxy.clone()).into(), caller.clone())?; + + }: _(RawOrigin::Signed(caller), proxy) + + close_proxy { + let u in 1 .. MAX_USERS; + + let proxy = open_activate_proxy::(u)?; + + }: _(RawOrigin::Signed(proxy)) + + deactivate_proxy { + let u in 1 .. MAX_USERS; + + let caller = funded_account::("caller", u); + let proxy = open_activate_proxy::(u)?; + + }: _(RawOrigin::Signed(caller), proxy) + + delegate { + let u in 1 .. MAX_USERS; + + let caller = funded_account::("caller", u); + let d: T::AccountId = funded_account::("delegate", u); + let balance = 1u32; + + }: _(RawOrigin::Signed(caller), d.into(), Conviction::Locked1x, balance.into()) + + undelegate { + let r in 1 .. MAX_REFERENDUMS; + + let other = funded_account::("other", 0); + let account_vote = account_vote::(); + + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(other.clone()).into(), ref_idx, account_vote.clone())?; + } + + let delegator = funded_account::("delegator", r); + let conviction = Conviction::Locked1x; + let balance = 1u32; + + Democracy::::delegate(RawOrigin::Signed(delegator.clone()).into(), other.clone().into(), conviction, balance.into())?; + + }: _(RawOrigin::Signed(delegator)) + + clear_public_proposals { + let p in 0 .. 100; + for i in 0 .. p { + add_proposal::(i)?; + } + }: _(RawOrigin::Root) + + note_preimage { + // Num of bytes in encoded proposal + let b in 0 .. 16_384; + + let caller = funded_account::("caller", b); + let encoded_proposal = vec![0; b as usize]; + }: _(RawOrigin::Signed(caller), encoded_proposal) + + note_imminent_preimage { + // Num of bytes in encoded proposal + let b in 0 .. 16_384; + // Length of dispatch queue + let d in 0 .. 100; + + let mut dispatch_queue = Vec::new(); + // d + 1 to include the one we are testing + for i in 0 .. d + 1 { + let encoded_proposal = vec![0; i as usize]; + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + let block_number = T::BlockNumber::zero(); + let referendum_index: ReferendumIndex = 0; + dispatch_queue.push((block_number, proposal_hash, referendum_index)) + } + >::put(dispatch_queue); + + let caller = funded_account::("caller", b); + let encoded_proposal = vec![0; d as usize]; + }: _(RawOrigin::Signed(caller), encoded_proposal) + + reap_preimage { + // Num of bytes in encoded proposal + let b in 0 .. 16_384; + // Length of dispatch queue + let d in 0 .. 100; + + let mut dispatch_queue = Vec::new(); + for i in 0 .. d { + let encoded_proposal = vec![0; i as usize]; + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + let block_number = T::BlockNumber::zero(); + let referendum_index: ReferendumIndex = 0; + dispatch_queue.push((block_number, proposal_hash, referendum_index)) + } + >::put(dispatch_queue); + + let caller = funded_account::("caller", d); + let encoded_proposal = vec![0; d as usize]; + Democracy::::note_preimage(RawOrigin::Signed(caller.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()); + + let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + + }: _(RawOrigin::Signed(caller), proposal_hash) + + unlock { + let u in 1 .. MAX_USERS; + + let caller = funded_account::("caller", u); + let locked_until = T::BlockNumber::zero(); + Locks::::insert(&caller, locked_until); + + T::Currency::extend_lock( + DEMOCRACY_ID, + &caller, + Bounded::max_value(), + WithdrawReason::Transfer.into() + ); + + let other = caller.clone(); + + }: _(RawOrigin::Signed(caller), other) + + remove_vote { + let r in 1 .. MAX_REFERENDUMS; + + let caller = funded_account::("caller", 0); + let account_vote = account_vote::(); + + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + } + + let referendum_index = r - 1; + + }: _(RawOrigin::Signed(caller), referendum_index) + + remove_other_vote { + let r in 1 .. MAX_REFERENDUMS; + + let other = funded_account::("other", r); + let account_vote = account_vote::(); + + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(other.clone()).into(), ref_idx, account_vote.clone())?; + } + + let referendum_index = r - 1; + ReferendumInfoOf::::insert( + referendum_index, + ReferendumInfo::Finished { end: T::BlockNumber::zero(), approved: true } + ); + let caller = funded_account::("caller", r); + + System::::set_block_number(T::EnactmentPeriod::get() * 10u32.into()); + + }: _(RawOrigin::Signed(caller), other, referendum_index) + + proxy_delegate { + let u in 1 .. MAX_USERS; + + let other: T::AccountId = account("other", u, SEED); + let proxy = open_activate_proxy::(u)?; + let conviction = Conviction::Locked1x; + let balance = 1u32; + + }: _(RawOrigin::Signed(proxy), other, conviction, balance.into()) + + proxy_undelegate { + let r in 1 .. MAX_REFERENDUMS; + + let other = funded_account::("other", 0); + let account_vote = account_vote::(); + + for i in 0 .. r { + let ref_idx = add_referendum::(i)?; + Democracy::::vote(RawOrigin::Signed(other.clone()).into(), ref_idx, account_vote.clone())?; + } + + let proxy = open_activate_proxy::(r)?; + let conviction = Conviction::Locked1x; + let balance = 1u32; + Democracy::::proxy_delegate(RawOrigin::Signed(proxy.clone()).into(), other, conviction, balance.into())?; + + }: _(RawOrigin::Signed(proxy)) + + proxy_remove_vote { + let u in 1 .. MAX_USERS; + + let referendum_index = add_referendum::(u)?; + let account_vote = account_vote::(); + let proxy = open_activate_proxy::(u)?; + + Democracy::::proxy_vote(RawOrigin::Signed(proxy.clone()).into(), referendum_index, account_vote)?; + + }: _(RawOrigin::Signed(proxy), referendum_index) +} diff --git a/frame/democracy/src/conviction.rs b/frame/democracy/src/conviction.rs new file mode 100644 index 0000000000000000000000000000000000000000..a057ee2a357503d0a51648874b92a9006c3d1087 --- /dev/null +++ b/frame/democracy/src/conviction.rs @@ -0,0 +1,113 @@ +// Copyright 2017-2020 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 . + +//! The conviction datatype. + +use sp_std::{result::Result, convert::TryFrom}; +use sp_runtime::{RuntimeDebug, traits::{Zero, Bounded, CheckedMul, CheckedDiv}}; +use codec::{Encode, Decode}; +use crate::types::Delegations; + +/// A value denoting the strength of conviction of a vote. +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)] +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< + B: From + 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/democracy/src/lib.rs b/frame/democracy/src/lib.rs index cbc766499fe6e4ff52d8911ff5db5616c4d779e1..7223b66a4e3781633d58c85fa99dec4e0ac1fdbe 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -51,9 +51,10 @@ //! account or an external origin) suggests that the system adopt. //! - **Referendum:** A proposal that is in the process of being voted on for //! either acceptance or rejection as a change to the system. -//! - **Proxy:** An account that votes on behalf of a separate "Stash" account +//! - **Proxy:** An account that has full voting power on behalf of a separate "Stash" account //! that holds the funds. -//! - **Delegation:** The act of granting your voting power to the decisions of another account. +//! - **Delegation:** The act of granting your voting power to the decisions of another account for +//! up to a certain conviction. //! //! ### Adaptive Quorum Biasing //! @@ -77,21 +78,33 @@ //! These calls can be made from any externally held account capable of creating //! a signed extrinsic. //! -//! - `propose` - Submits a sensitive action, represented as a hash. -//! Requires a deposit. -//! - `second` - Signals agreement with a proposal, moves it higher on the -//! proposal queue, and requires a matching deposit to the original. -//! - `vote` - Votes in a referendum, either the vote is "Aye" to enact the -//! proposal or "Nay" to keep the status quo. -//! - `proxy_vote` - Votes in a referendum on behalf of a stash account. +//! Basic actions: +//! - `propose` - Submits a sensitive action, represented as a hash. Requires a deposit. +//! - `second` - Signals agreement with a proposal, moves it higher on the proposal queue, and +//! requires a matching deposit to the original. +//! - `vote` - Votes in a referendum, either the vote is "Aye" to enact the proposal or "Nay" to +//! keep the status quo. +//! - `unvote` - Cancel a previous vote, this must be done by the voter before the vote ends. +//! - `delegate` - Delegates the voting power (tokens * conviction) to another account. +//! - `undelegate` - Stops the delegation of voting power to another account. +//! +//! Administration actions that can be done to any account: +//! - `reap_vote` - Remove some account's expired votes. +//! - `unlock` - Redetermine the account's balance lock, potentially making tokens available. +//! +//! Proxy administration: //! - `activate_proxy` - Activates a proxy that is already open to the sender. //! - `close_proxy` - Clears the proxy status, called by the proxy. -//! - `deactivate_proxy` - Deactivates a proxy back to the open status, called by -//! the stash. +//! - `deactivate_proxy` - Deactivates a proxy back to the open status, called by the stash. //! - `open_proxy` - Opens a proxy account on behalf of the sender. -//! - `delegate` - Delegates the voting power (tokens * conviction) to another -//! account. -//! - `undelegate` - Stops the delegation of voting power to another account. +//! +//! Proxy actions: +//! - `proxy_vote` - Votes in a referendum on behalf of a stash account. +//! - `proxy_unvote` - Cancel a previous vote, done on behalf of the voter by a proxy. +//! - `proxy_delegate` - Delegate voting power, done on behalf of the voter by a proxy. +//! - `proxy_undelegate` - Stop delegating voting power, done on behalf of the voter by a proxy. +//! +//! Preimage actions: //! - `note_preimage` - Registers the preimage for an upcoming proposal, requires //! a deposit that is returned once the proposal is enacted. //! - `note_imminent_preimage` - Registers the preimage for an upcoming proposal. @@ -99,7 +112,6 @@ //! - `reap_preimage` - Removes the preimage for an expired proposal. Will only //! work under the condition that it's the same account that noted it and //! after the voting period, OR it's a different account after the enactment period. -//! - `unlock` - Unlocks tokens that have an expired lock. //! //! #### Cancellation Origin //! @@ -152,15 +164,13 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_std::prelude::*; -use sp_std::{result, convert::TryFrom}; use sp_runtime::{ - RuntimeDebug, DispatchResult, - traits::{Zero, Bounded, CheckedMul, CheckedDiv, EnsureOrigin, Hash, Dispatchable, Saturating}, + DispatchResult, DispatchError, traits::{Zero, EnsureOrigin, Hash, Dispatchable, Saturating}, }; -use codec::{Ref, Encode, Decode, Input, Output}; +use codec::{Ref, Decode}; use frame_support::{ - decl_module, decl_storage, decl_event, decl_error, ensure, Parameter, IterableStorageMap, - weights::SimpleDispatchInfo, + decl_module, decl_storage, decl_event, decl_error, ensure, Parameter, + weights::{SimpleDispatchInfo, Weight, WeighData}, traits::{ Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier, Get, OnUnbalanced, BalanceStatus @@ -169,8 +179,19 @@ use frame_support::{ use frame_system::{self as system, ensure_signed, ensure_root}; mod vote_threshold; +mod vote; +mod conviction; +mod types; pub use vote_threshold::{Approved, VoteThreshold}; -use frame_support::traits::MigrateAccount; +pub use vote::{Vote, AccountVote, Voting}; +pub use conviction::Conviction; +pub use types::{ReferendumInfo, ReferendumStatus, ProxyState, Tally, UnvoteScope, Delegations}; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; const DEMOCRACY_ID: LockIdentifier = *b"democrac"; @@ -180,134 +201,9 @@ pub type PropIndex = u32; /// A referendum index. pub type ReferendumIndex = u32; -/// A value denoting the strength of conviction of a vote. -#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)] -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::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. - 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. - fn votes< - B: From + Zero + Copy + CheckedMul + CheckedDiv + Bounded - >(self, balance: B) -> (B, B) { - match self { - Conviction::None => { - let r = balance.checked_div(&10u8.into()).unwrap_or_else(Zero::zero); - (r, r) - } - x => ( - balance.checked_mul(&u8::from(x).into()).unwrap_or_else(B::max_value), - balance, - ) - } - } -} - -impl Bounded for Conviction { - fn min_value() -> Self { - Conviction::None - } - - fn max_value() -> Self { - Conviction::Locked6x - } -} - -const MAX_RECURSION_LIMIT: u32 = 16; - -/// A number of lock periods, plus a vote, one way or the other. -#[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)] -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 codec::EncodeLike for Vote {} - -impl Decode for Vote { - fn decode(input: &mut I) -> core::result::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"))?, - }) - } -} - type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type NegativeImbalanceOf = -<::Currency as Currency<::AccountId>>::NegativeImbalance; + <::Currency as Currency<::AccountId>>::NegativeImbalance; pub trait Trait: frame_system::Trait + Sized { type Proposal: Parameter + Dispatchable; @@ -345,13 +241,23 @@ pub trait Trait: frame_system::Trait + Sized { /// a negative-turnout-bias (default-carries) referendum. type ExternalDefaultOrigin: EnsureOrigin; - /// Origin from which the next referendum proposed by the external majority may be immediately - /// tabled to vote asynchronously in a similar manner to the emergency origin. It remains a - /// majority-carries vote. + /// 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; - /// Minimum voting period allowed for an fast-track/emergency referendum. - type EmergencyVotingPeriod: Get; + /// 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. + type InstantAllowed: Get; + + /// Minimum voting period allowed for a fast-track referendum. + type FastTrackVotingPeriod: Get; /// Origin from which any referendum may be cancelled in an emergency. type CancellationOrigin: EnsureOrigin; @@ -369,100 +275,60 @@ pub trait Trait: frame_system::Trait + Sized { type Slash: OnUnbalanced>; } -/// Info regarding an ongoing referendum. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] -pub struct ReferendumInfo { - /// When voting on this referendum will end. - end: BlockNumber, - /// The hash of the proposal being voted on. - proposal_hash: Hash, - /// The thresholding mechanism to determine whether it passed. - threshold: VoteThreshold, - /// The delay (in blocks) to wait after a successful referendum before deploying. - delay: BlockNumber, -} - -impl ReferendumInfo { - /// Create a new instance. - pub fn new( - end: BlockNumber, - proposal_hash: Hash, - threshold: VoteThreshold, - delay: BlockNumber - ) -> Self { - ReferendumInfo { end, proposal_hash, threshold, delay } - } -} - -/// State of a proxy voting account. -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] -pub enum ProxyState { - /// Account is open to becoming a proxy but is not yet assigned. - Open(AccountId), - /// Account is actively being a proxy. - Active(AccountId), -} - -impl ProxyState { - fn as_active(self) -> Option { - match self { - ProxyState::Active(a) => Some(a), - ProxyState::Open(_) => None, - } - } -} - decl_storage! { trait Store for Module as Democracy { + // 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. pub PublicPropCount get(fn public_prop_count) build(|_| 0 as PropIndex) : PropIndex; /// The public proposals. Unsorted. The second item is the proposal's hash. pub PublicProps get(fn public_props): Vec<(PropIndex, T::Hash, T::AccountId)>; + /// Those who have locked a deposit. + pub DepositOf get(fn deposit_of): + map hasher(twox_64_concat) PropIndex => Option<(BalanceOf, Vec)>; + /// 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 pub Preimages: map hasher(identity) T::Hash => Option<(Vec, T::AccountId, BalanceOf, T::BlockNumber)>; - /// Those who have locked a deposit. - pub DepositOf get(fn deposit_of): - map hasher(twox_64_concat) PropIndex => Option<(BalanceOf, Vec)>; /// The next free referendum index, aka the number of referenda started so far. pub ReferendumCount get(fn referendum_count) build(|_| 0 as ReferendumIndex): ReferendumIndex; /// The lowest referendum index representing an unbaked referendum. Equal to /// `ReferendumCount` if there isn't a unbaked referendum. pub LowestUnbaked get(fn lowest_unbaked) build(|_| 0 as ReferendumIndex): ReferendumIndex; + /// Information concerning any given referendum. pub ReferendumInfoOf get(fn referendum_info): map hasher(twox_64_concat) ReferendumIndex - => Option>; + => Option>>; + + // TODO: Refactor DispatchQueue into its own pallet. + // https://github.com/paritytech/substrate/issues/5322 /// Queue of successful referenda to be dispatched. Stored ordered by block number. pub DispatchQueue get(fn dispatch_queue): Vec<(T::BlockNumber, T::Hash, ReferendumIndex)>; - /// Get the voters for the current proposal. - pub VotersFor get(fn voters_for): - map hasher(twox_64_concat) ReferendumIndex => Vec; - - /// Get the vote in a given referendum of a particular voter. The result is meaningful only - /// if `voters_for` includes the voter when called with the referendum (you'll get the - /// default `Vote` value otherwise). If you don't want to check `voters_for`, then you can - /// also check for simple existence with `VoteOf::contains_key` first. - pub VoteOf get(fn vote_of): map hasher(twox_64_concat) (ReferendumIndex, T::AccountId) => Vote; + /// All votes for a particular voter. We store the balance for the number of votes that we + /// have recorded. The second item is the total amount of delegations, that will be added. + pub VotingOf: map hasher(twox_64_concat) T::AccountId => Voting, T::AccountId, T::BlockNumber>; /// Who is able to vote for whom. Value is the fund-holding account, key is the /// vote-transaction-sending account. + // TODO: Refactor proxy into its own pallet. + // https://github.com/paritytech/substrate/issues/5322 pub Proxy get(fn proxy): map hasher(twox_64_concat) T::AccountId => Option>; - /// Get the account (and lock periods) to which another account is delegating vote. - pub Delegations get(fn delegations): - map hasher(twox_64_concat) T::AccountId => (T::AccountId, Conviction); - /// 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. pub Locks get(locks): map hasher(twox_64_concat) T::AccountId => Option; /// 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 pub LastTabledWasExternal: bool; /// The referendum to be tabled whenever it would be valid to table an external proposal. @@ -560,7 +426,7 @@ decl_error! { /// Not imminent NotImminent, /// Too early - Early, + TooEarly, /// Imminent Imminent, /// Preimage not found @@ -580,34 +446,28 @@ decl_error! { /// A proxy-pairing was attempted to an account that was open to another account. WrongOpen, /// A proxy-de-pairing was attempted to an account that was not active. - NotActive - } -} - -impl MigrateAccount for Module { - fn migrate_account(a: &T::AccountId) { - Proxy::::migrate_key_from_blake(a); - Locks::::migrate_key_from_blake(a); - Delegations::::migrate_key_from_blake(a); - for i in LowestUnbaked::get()..ReferendumCount::get() { - VoteOf::::migrate_key_from_blake((i, a)); - } - } -} - -mod migration { - use super::*; - pub fn migrate() { - Blacklist::::remove_all(); - Cancellations::::remove_all(); - for i in LowestUnbaked::get()..ReferendumCount::get() { - VotersFor::::migrate_key_from_blake(i); - ReferendumInfoOf::::migrate_key_from_blake(i); - } - for (p, h, _) in PublicProps::::get().into_iter() { - DepositOf::::migrate_key_from_blake(p); - Preimages::::migrate_key_from_blake(h); - } + NotActive, + /// The given account did not vote on the referendum. + NotVoter, + /// The actor has no permission to conduct the action. + NoPermission, + /// The account is already delegating. + AlreadyDelegating, + /// An unexpected integer overflow occurred. + Overflow, + /// An unexpected integer underflow occurred. + Underflow, + /// Too high a balance was provided that the account cannot afford. + InsufficientFunds, + /// The account is not currently delegating. + NotDelegating, + /// The account currently has votes attached to it and the operation cannot succeed until + /// these are removed, either through `unvote` or `reap_vote`. + VotesExist, + /// The instant referendum origin is currently disallowed. + InstantNotAllowed, + /// Delegation to oneself makes no sense. + Nonsense, } } @@ -615,10 +475,6 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin { type Error = Error; - fn on_runtime_upgrade() { - migration::migrate::(); - } - /// The minimum period of locking and the period between a proposal being approved and enacted. /// /// It should generally be a little more than the unstake period to ensure that @@ -636,7 +492,7 @@ decl_module! { const MinimumDeposit: BalanceOf = T::MinimumDeposit::get(); /// Minimum voting period allowed for an emergency referendum. - const EmergencyVotingPeriod: T::BlockNumber = T::EmergencyVotingPeriod::get(); + const FastTrackVotingPeriod: T::BlockNumber = T::FastTrackVotingPeriod::get(); /// Period in blocks where an external proposal may not be re-submitted after being vetoed. const CooloffPeriod: T::BlockNumber = T::CooloffPeriod::get(); @@ -646,6 +502,12 @@ decl_module! { fn deposit_event() = default; + fn on_runtime_upgrade() -> Weight { + Self::migrate(); + + SimpleDispatchInfo::default().weigh_data(()) + } + /// Propose a sensitive action to be taken. /// /// The dispatch origin of this call must be _Signed_ and the sender must @@ -657,7 +519,8 @@ decl_module! { /// Emits `Proposed`. /// /// # - /// - `O(1)`. + /// - `O(P)` + /// - P is the number proposals in the `PublicProps` vec. /// - Two DB changes, one DB entry. /// # #[weight = SimpleDispatchInfo::FixedNormal(5_000_000)] @@ -687,7 +550,8 @@ decl_module! { /// - `proposal`: The index of the proposal to second. /// /// # - /// - `O(1)`. + /// - `O(S)`. + /// - S is the number of seconds a proposal already has. /// - One DB entry. /// # #[weight = SimpleDispatchInfo::FixedNormal(5_000_000)] @@ -709,16 +573,17 @@ decl_module! { /// - `vote`: The vote configuration. /// /// # - /// - `O(1)`. + /// - `O(R)`. + /// - R is the number of referendums the voter has voted on. /// - One DB change, one DB entry. /// # #[weight = SimpleDispatchInfo::FixedNormal(200_000)] fn vote(origin, #[compact] ref_index: ReferendumIndex, - vote: Vote + vote: AccountVote>, ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::do_vote(who, ref_index, vote) + Self::try_vote(&who, ref_index, vote) } /// Vote in a referendum on behalf of a stash. If `vote.is_aye()`, the vote is to enact @@ -736,11 +601,11 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(200_000)] fn proxy_vote(origin, #[compact] ref_index: ReferendumIndex, - vote: Vote + vote: AccountVote>, ) -> DispatchResult { let who = ensure_signed(origin)?; let voter = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; - Self::do_vote(voter, ref_index, vote) + Self::try_vote(&voter, ref_index, vote) } /// Schedule an emergency cancellation of a referendum. Cannot happen twice to the same @@ -751,18 +616,18 @@ decl_module! { /// -`ref_index`: The index of the referendum to cancel. /// /// # - /// - Depends on size of storage vec `VotersFor` for this referendum. + /// - `O(1)`. /// # #[weight = SimpleDispatchInfo::FixedOperational(500_000)] fn emergency_cancel(origin, ref_index: ReferendumIndex) { T::CancellationOrigin::ensure_origin(origin)?; - let info = Self::referendum_info(ref_index).ok_or(Error::::BadIndex)?; - let h = info.proposal_hash; + let status = Self::referendum_status(ref_index)?; + let h = status.proposal_hash; ensure!(!>::contains_key(h), Error::::AlreadyCanceled); >::insert(h, true); - Self::clear_referendum(ref_index); + Self::internal_cancel_referendum(ref_index); } /// Schedule a referendum to be tabled once it is legal to schedule an external @@ -837,7 +702,7 @@ decl_module! { /// /// - `proposal_hash`: The hash of the current external proposal. /// - `voting_period`: The period that is allowed for voting on this proposal. Increased to - /// `EmergencyVotingPeriod` if too low. + /// `FastTrackVotingPeriod` if too low. /// - `delay`: The number of block after voting has ended in approval and this should be /// enacted. This doesn't have a minimum amount. /// @@ -852,10 +717,27 @@ decl_module! { fn fast_track(origin, proposal_hash: T::Hash, voting_period: T::BlockNumber, - delay: T::BlockNumber + delay: T::BlockNumber, ) { - T::FastTrackOrigin::ensure_origin(origin)?; - let (e_proposal_hash, threshold) = >::get().ok_or(Error::::ProposalMissing)?; + // Rather complicated bit of code to ensure that either: + // - `voting_period` is at least `FastTrackVotingPeriod` and `origin` is `FastTrackOrigin`; or + // - `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 { + 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) = >::get() + .ok_or(Error::::ProposalMissing)?; ensure!( threshold != VoteThreshold::SuperMajorityApprove, Error::::NotSimpleMajority, @@ -864,9 +746,7 @@ decl_module! { >::kill(); let now = >::block_number(); - // We don't consider it an error if `vote_period` is too low, like `emergency_propose`. - let period = voting_period.max(T::EmergencyVotingPeriod::get()); - Self::inject_referendum(now + period, proposal_hash, threshold, delay); + Self::inject_referendum(now + voting_period, proposal_hash, threshold, delay); } /// Veto and blacklist the external proposal hash. @@ -882,6 +762,7 @@ decl_module! { /// - One DB clear. /// - Performs a binary search on `existing_vetoers` which should not /// be very large. + /// - O(log v), v is number of `existing_vetoers` /// # #[weight = SimpleDispatchInfo::FixedNormal(200_000)] fn veto_external(origin, proposal_hash: T::Hash) { @@ -919,7 +800,7 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedOperational(10_000)] fn cancel_referendum(origin, #[compact] ref_index: ReferendumIndex) { ensure_root(origin)?; - Self::clear_referendum(ref_index); + Self::internal_cancel_referendum(ref_index); } /// Cancel a proposal queued for enactment. @@ -930,6 +811,7 @@ decl_module! { /// /// # /// - One DB change. + /// - O(d) where d is the items in the dispatch queue. /// # #[weight = SimpleDispatchInfo::FixedOperational(10_000)] fn cancel_queued(origin, which: ReferendumIndex) { @@ -941,10 +823,12 @@ decl_module! { >::put(items); } - fn on_initialize(n: T::BlockNumber) { + fn on_initialize(n: T::BlockNumber) -> Weight { if let Err(e) = Self::begin_block(n) { sp_runtime::print(e); } + + SimpleDispatchInfo::default().weigh_data(()) } /// Specify a proxy that is already open to us. Called by the stash. @@ -1018,43 +902,39 @@ decl_module! { })?; } - /// Delegate vote. + /// Delegate the voting power (with some given conviction) of the sending account. /// - /// Currency is locked indefinitely for as long as it's delegated. + /// 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_. + /// 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 to make a delegate of the sender. - /// - `conviction`: The conviction that will be attached to the delegated - /// votes. + /// - `to`: The account whose voting the `target` account's voting power will follow. + /// - `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`. /// /// # - /// - One extra DB entry. /// # #[weight = SimpleDispatchInfo::FixedNormal(500_000)] - pub fn delegate(origin, to: T::AccountId, conviction: Conviction) { + pub fn delegate(origin, to: T::AccountId, conviction: Conviction, balance: BalanceOf) { let who = ensure_signed(origin)?; - >::insert(&who, (&to, conviction)); - // Currency is locked indefinitely as long as it's delegated. - T::Currency::extend_lock( - DEMOCRACY_ID, - &who, - Bounded::max_value(), - WithdrawReason::Transfer.into() - ); - Locks::::remove(&who); - Self::deposit_event(RawEvent::Delegated(who, to)); + Self::try_delegate(who, to, conviction, balance)?; } - /// Undelegate vote. + /// Undelegate the voting power of the sending account. /// - /// Must be sent from an account that has called delegate previously. - /// The tokens will be reduced from an indefinite lock to the maximum - /// possible according to the conviction of the prior delegation. + /// Tokens may be unlocked following once an amount of time consistent with the lock period + /// of the conviction with which the delegation was issued. /// - /// The dispatch origin of this call must be _Signed_. + /// The dispatch origin of this call must be _Signed_ and the signing account must be + /// currently delegating. /// /// Emits `Undelegated`. /// @@ -1064,19 +944,7 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn undelegate(origin) { let who = ensure_signed(origin)?; - ensure!(>::contains_key(&who), Error::::NotDelegated); - let (_, conviction) = >::take(&who); - // Indefinite lock is reduced to the maximum voting lock that could be possible. - let now = >::block_number(); - let locked_until = now + T::EnactmentPeriod::get() * conviction.lock_periods().into(); - Locks::::insert(&who, locked_until); - T::Currency::set_lock( - DEMOCRACY_ID, - &who, - Bounded::max_value(), - WithdrawReason::Transfer.into(), - ); - Self::deposit_event(RawEvent::Undelegated(who)); + Self::try_undelegate(who)?; } /// Clears all public proposals. @@ -1133,7 +1001,7 @@ decl_module! { /// Emits `PreimageNoted`. /// /// # - /// - Dependent on the size of `encoded_proposal`. + /// - Dependent on the size of `encoded_proposal` and length of dispatch queue. /// # #[weight = SimpleDispatchInfo::FixedNormal(100_000)] fn note_imminent_preimage(origin, encoded_proposal: Vec) { @@ -1174,7 +1042,7 @@ decl_module! { let now = >::block_number(); let (voting, enactment) = (T::VotingPeriod::get(), T::EnactmentPeriod::get()); let additional = if who == old { Zero::zero() } else { enactment }; - ensure!(now >= then + voting + additional, Error::::Early); + ensure!(now >= then + voting + additional, Error::::TooEarly); let queue = >::get(); ensure!(!queue.iter().any(|item| &item.1 == &proposal_hash), Error::::Imminent); @@ -1190,21 +1058,13 @@ decl_module! { /// /// - `target`: The account to remove the lock on. /// - /// Emits `Unlocked`. - /// /// # /// - `O(1)`. /// # #[weight = SimpleDispatchInfo::FixedNormal(10_000)] fn unlock(origin, target: T::AccountId) { ensure_signed(origin)?; - - let expiry = Locks::::get(&target).ok_or(Error::::NotLocked)?; - ensure!(expiry <= system::Module::::block_number(), Error::::NotExpired); - - T::Currency::remove_lock(DEMOCRACY_ID, &target); - Locks::::remove(&target); - Self::deposit_event(RawEvent::Unlocked(target)); + Self::update_lock(&target); } /// Become a proxy. @@ -1230,119 +1090,185 @@ decl_module! { *a = Some(ProxyState::Open(target)); }); } + + /// Remove a vote for a referendum. + /// + /// If: + /// - the referendum was cancelled, or + /// - the referendum is ongoing, or + /// - the referendum 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 referendum 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 referendum `index`. + /// + /// - `index`: The index of referendum of the vote to be removed. + /// + /// # + /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(10_000)] + fn remove_vote(origin, index: ReferendumIndex) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::try_remove_vote(&who, index, UnvoteScope::Any) + } + + /// Remove a vote for a referendum. + /// + /// 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 referendum was cancelled, because the voter lost the referendum 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 + /// referendum `index`. + /// - `index`: The index of referendum of the vote to be removed. + /// + /// # + /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(10_000)] + fn remove_other_vote(origin, target: T::AccountId, index: ReferendumIndex) -> DispatchResult { + let who = ensure_signed(origin)?; + let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; + Self::try_remove_vote(&target, index, scope)?; + Ok(()) + } + + /// Delegate the voting power (with some given conviction) of a proxied account. + /// + /// 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 have + /// been set as the proxy account for `target`. + /// + /// - `target`: The account whole voting power shall be delegated and whose balance locked. + /// This 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. + /// - `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 = SimpleDispatchInfo::FixedNormal(500_000)] + pub fn proxy_delegate(origin, + to: T::AccountId, + conviction: Conviction, + balance: BalanceOf, + ) { + let who = ensure_signed(origin)?; + let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; + Self::try_delegate(target, to, conviction, balance)?; + } + + /// Undelegate the voting power of a proxied account. + /// + /// Tokens may be unlocked following once an amount of time consistent with the lock period + /// of the conviction with which the delegation was issued. + /// + /// The dispatch origin of this call must be _Signed_ and the signing account must be a + /// proxy for some other account which is currently delegating. + /// + /// Emits `Undelegated`. + /// + /// # + /// - O(1). + /// # + #[weight = SimpleDispatchInfo::FixedNormal(500_000)] + fn proxy_undelegate(origin) { + let who = ensure_signed(origin)?; + let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; + Self::try_undelegate(target)?; + } + + /// Remove a proxied vote for a referendum. + /// + /// Exactly equivalent to `remove_vote` except that it operates on the account that the + /// sender is a proxy for. + /// + /// The dispatch origin of this call must be _Signed_ and the signing account must be a + /// proxy for some other account which has a registered vote for the referendum of `index`. + /// + /// - `index`: The index of referendum of the vote to be removed. + /// + /// # + /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(10_000)] + fn proxy_remove_vote(origin, index: ReferendumIndex) -> DispatchResult { + let who = ensure_signed(origin)?; + let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::::NotProxy)?; + Self::try_remove_vote(&target, index, UnvoteScope::Any) + } } } impl Module { + fn migrate() { + use frame_support::{Twox64Concat, migration::{StorageKeyIterator, remove_storage_prefix}}; + remove_storage_prefix(b"Democracy", b"VotesOf", &[]); + remove_storage_prefix(b"Democracy", b"VotersFor", &[]); + remove_storage_prefix(b"Democracy", b"Delegations", &[]); + for (who, (end, proposal_hash, threshold, delay)) + in StorageKeyIterator::< + ReferendumIndex, + (T::BlockNumber, T::Hash, VoteThreshold, T::BlockNumber), + Twox64Concat, + >::new(b"Democracy", b"ReferendumInfoOf").drain() + { + let status = ReferendumStatus { + end, proposal_hash, threshold, delay, tally: Tally::default() + }; + ReferendumInfoOf::::insert(who, ReferendumInfo::Ongoing(status)) + } + } + // exposed immutables. /// Get the amount locked in support of `proposal`; `None` if proposal isn't a valid proposal /// index. - pub fn locked_for(proposal: PropIndex) -> Option> { + pub fn backing_for(proposal: PropIndex) -> Option> { Self::deposit_of(proposal).map(|(d, l)| d * (l.len() as u32).into()) } - /// Return true if `ref_index` is an on-going referendum. - pub fn is_active_referendum(ref_index: ReferendumIndex) -> bool { - >::contains_key(ref_index) - } - - /// Get all referenda currently active. - pub fn active_referenda() - -> Vec<(ReferendumIndex, ReferendumInfo)> - { - let next = Self::lowest_unbaked(); - let last = Self::referendum_count(); - (next..last).into_iter() - .filter_map(|i| Self::referendum_info(i).map(|info| (i, info))) - .collect() - } - /// Get all referenda ready for tally at block `n`. pub fn maturing_referenda_at( n: T::BlockNumber - ) -> Vec<(ReferendumIndex, ReferendumInfo)> { + ) -> Vec<(ReferendumIndex, ReferendumStatus>)> { let next = Self::lowest_unbaked(); let last = Self::referendum_count(); (next..last).into_iter() - .filter_map(|i| Self::referendum_info(i).map(|info| (i, info))) - .filter(|&(_, ref info)| info.end == n) + .map(|i| (i, Self::referendum_info(i))) + .filter_map(|(i, maybe_info)| match maybe_info { + Some(ReferendumInfo::Ongoing(status)) => Some((i, status)), + _ => None, + }) + .filter(|(_, status)| status.end == n) .collect() } - /// Get the voters for the current proposal. - pub fn tally(ref_index: ReferendumIndex) -> (BalanceOf, BalanceOf, BalanceOf) { - let (approve, against, capital): - (BalanceOf, BalanceOf, BalanceOf) = Self::voters_for(ref_index) - .iter() - .map(|voter| ( - T::Currency::total_balance(voter), Self::vote_of((ref_index, voter.clone())) - )) - .map(|(balance, Vote { aye, conviction })| { - let (votes, turnout) = conviction.votes(balance); - if aye { - (votes, Zero::zero(), turnout) - } else { - (Zero::zero(), votes, turnout) - } - }).fold( - (Zero::zero(), Zero::zero(), Zero::zero()), - |(a, b, c), (d, e, f)| (a + d, b + e, c + f) - ); - let (del_approve, del_against, del_capital) = Self::tally_delegation(ref_index); - (approve + del_approve, against + del_against, capital + del_capital) - } - - /// Get the delegated voters for the current proposal. - /// I think this goes into a worker once https://github.com/paritytech/substrate/issues/1458 is - /// done. - fn tally_delegation(ref_index: ReferendumIndex) -> (BalanceOf, BalanceOf, BalanceOf) { - Self::voters_for(ref_index).iter().fold( - (Zero::zero(), Zero::zero(), Zero::zero()), - |(approve_acc, against_acc, turnout_acc), voter| { - let Vote { aye, conviction } = Self::vote_of((ref_index, voter.clone())); - let (votes, turnout) = Self::delegated_votes( - ref_index, - voter.clone(), - conviction, - MAX_RECURSION_LIMIT - ); - if aye { - (approve_acc + votes, against_acc, turnout_acc + turnout) - } else { - (approve_acc, against_acc + votes, turnout_acc + turnout) - } - } - ) - } - - fn delegated_votes( - ref_index: ReferendumIndex, - to: T::AccountId, - parent_conviction: Conviction, - recursion_limit: u32, - ) -> (BalanceOf, BalanceOf) { - if recursion_limit == 0 { return (Zero::zero(), Zero::zero()); } - >::iter() - .filter(|(delegator, (delegate, _))| - *delegate == to && !>::contains_key(&(ref_index, delegator.clone())) - ).fold( - (Zero::zero(), Zero::zero()), - |(votes_acc, turnout_acc), (delegator, (_delegate, max_conviction))| { - let conviction = Conviction::min(parent_conviction, max_conviction); - let balance = T::Currency::total_balance(&delegator); - let (votes, turnout) = conviction.votes(balance); - let (del_votes, del_turnout) = Self::delegated_votes( - ref_index, - delegator, - conviction, - recursion_limit - 1 - ); - (votes_acc + votes + del_votes, turnout_acc + turnout + del_turnout) - } - ) - } - // Exposed mutables. #[cfg(feature = "std")] @@ -1372,77 +1298,276 @@ impl Module { /// Remove a referendum. pub fn internal_cancel_referendum(ref_index: ReferendumIndex) { Self::deposit_event(RawEvent::Cancelled(ref_index)); - >::clear_referendum(ref_index); + ReferendumInfoOf::::remove(ref_index); } // private. - /// Actually enact a vote, if legit. - fn do_vote(who: T::AccountId, ref_index: ReferendumIndex, vote: Vote) -> DispatchResult { - ensure!(Self::is_active_referendum(ref_index), Error::::ReferendumInvalid); - if !>::contains_key((ref_index, &who)) { - >::append_or_insert(ref_index, &[&who][..]); + /// Ok if the given referendum is active, Err otherwise + fn ensure_ongoing(r: ReferendumInfo>) + -> Result>, DispatchError> + { + match r { + ReferendumInfo::Ongoing(s) => Ok(s), + _ => Err(Error::::ReferendumInvalid.into()), } - >::insert((ref_index, &who), vote); - Ok(()) } - /// Start a referendum - fn inject_referendum( - end: T::BlockNumber, - proposal_hash: T::Hash, - threshold: VoteThreshold, - delay: T::BlockNumber, - ) -> ReferendumIndex { - let ref_index = Self::referendum_count(); - ReferendumCount::put(ref_index + 1); - let item = ReferendumInfo { end, proposal_hash, threshold, delay }; - >::insert(ref_index, item); - Self::deposit_event(RawEvent::Started(ref_index, threshold)); - ref_index + fn referendum_status(ref_index: ReferendumIndex) + -> Result>, DispatchError> + { + let info = ReferendumInfoOf::::get(ref_index) + .ok_or(Error::::ReferendumInvalid)?; + Self::ensure_ongoing(info) } - /// Remove all info on a referendum. - fn clear_referendum(ref_index: ReferendumIndex) { - >::remove(ref_index); - - LowestUnbaked::mutate(|i| if *i == ref_index { - *i += 1; - let end = ReferendumCount::get(); - while !Self::is_active_referendum(*i) && *i < end { - *i += 1; + /// Actually enact a vote, if legit. + fn try_vote(who: &T::AccountId, ref_index: ReferendumIndex, vote: AccountVote>) -> DispatchResult { + let mut status = Self::referendum_status(ref_index)?; + ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); + VotingOf::::try_mutate(who, |voting| -> DispatchResult { + if let Voting::Direct { ref mut votes, delegations, .. } = voting { + match votes.binary_search_by_key(&ref_index, |i| i.0) { + Ok(i) => { + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; + if let Some(approve) = votes[i].1.as_standard() { + status.tally.reduce(approve, *delegations); + } + votes[i].1 = vote; + } + Err(i) => votes.insert(i, (ref_index, vote)), + } + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.add(vote).ok_or(Error::::Overflow)?; + if let Some(approve) = vote.as_standard() { + status.tally.increase(approve, *delegations); + } + Ok(()) + } else { + Err(Error::::AlreadyDelegating.into()) } - }); - >::remove(ref_index); - for v in Self::voters_for(ref_index) { - >::remove((ref_index, v)); - } + })?; + // Extend the lock to `balance` (rather than setting it) since we don't know what other + // votes are in place. + T::Currency::extend_lock( + DEMOCRACY_ID, + who, + vote.balance(), + WithdrawReason::Transfer.into() + ); + ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); + Ok(()) } - /// Enact a proposal from a referendum. - fn enact_proposal(proposal_hash: T::Hash, index: ReferendumIndex) -> DispatchResult { - if let Some((encoded_proposal, who, amount, _)) = >::take(&proposal_hash) { - if let Ok(proposal) = T::Proposal::decode(&mut &encoded_proposal[..]) { - let _ = T::Currency::unreserve(&who, amount); - Self::deposit_event(RawEvent::PreimageUsed(proposal_hash, who, amount)); - - let ok = proposal.dispatch(frame_system::RawOrigin::Root.into()).is_ok(); - Self::deposit_event(RawEvent::Executed(index, ok)); + /// Remove the account's vote for the given referendum if possible. This is possible when: + /// - The referendum has not finished. + /// - The referendum has finished and the voter lost their direction. + /// - The referendum 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, ref_index: ReferendumIndex, scope: UnvoteScope) -> DispatchResult { + let info = ReferendumInfoOf::::get(ref_index); + VotingOf::::try_mutate(who, |voting| -> DispatchResult { + if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting { + let i = votes.binary_search_by_key(&ref_index, |i| i.0).map_err(|_| Error::::NotVoter)?; + match info { + Some(ReferendumInfo::Ongoing(mut status)) => { + ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); + // Shouldn't be possible to fail, but we handle it gracefully. + status.tally.remove(votes[i].1).ok_or(Error::::Underflow)?; + if let Some(approve) = votes[i].1.as_standard() { + status.tally.reduce(approve, *delegations); + } + ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); + } + Some(ReferendumInfo::Finished{end, approved}) => + if let Some((lock_periods, balance)) = votes[i].1.locked_if(approved) { + let unlock_at = end + T::EnactmentPeriod::get() * lock_periods.into(); + let now = system::Module::::block_number(); + if now < unlock_at { + ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); + prior.accumulate(unlock_at, balance) + } + }, + None => {} // Referendum was cancelled. + } + votes.remove(i); + } + Ok(()) + })?; + Ok(()) + } - Ok(()) - } else { - T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); - Self::deposit_event(RawEvent::PreimageInvalid(proposal_hash, index)); - Err(Error::::PreimageInvalid.into()) + fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations>) { + VotingOf::::mutate(who, |voting| match voting { + Voting::Delegating { delegations, .. } => + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_add(amount), + Voting::Direct { votes, delegations, .. } => { + *delegations = delegations.saturating_add(amount); + for &(ref_index, account_vote) in votes.iter() { + if let AccountVote::Standard { vote, .. } = account_vote { + ReferendumInfoOf::::mutate(ref_index, |maybe_info| + if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { + status.tally.increase(vote.aye, amount); + } + ); + } + } } - } else { - Self::deposit_event(RawEvent::PreimageMissing(proposal_hash, index)); - Err(Error::::PreimageMissing.into()) - } + }) } - /// Table the next waiting proposal for a vote. - fn launch_next(now: T::BlockNumber) -> DispatchResult { + fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations>) { + VotingOf::::mutate(who, |voting| match voting { + Voting::Delegating { delegations, .. } => + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_sub(amount), + Voting::Direct { votes, delegations, .. } => { + *delegations = delegations.saturating_sub(amount); + for &(ref_index, account_vote) in votes.iter() { + if let AccountVote::Standard { vote, .. } = account_vote { + ReferendumInfoOf::::mutate(ref_index, |maybe_info| + if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info { + status.tally.reduce(vote.aye, amount); + } + ); + } + } + } + }) + } + + /// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`. + fn try_delegate( + who: T::AccountId, + target: T::AccountId, + conviction: Conviction, + balance: BalanceOf, + ) -> DispatchResult { + ensure!(who != target, Error::::Nonsense); + ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); + VotingOf::::try_mutate(&who, |voting| -> DispatchResult { + let mut old = Voting::Delegating { + balance, + target: target.clone(), + conviction, + delegations: Default::default(), + prior: Default::default(), + }; + sp_std::mem::swap(&mut old, voting); + match old { + Voting::Delegating { balance, target, conviction, delegations, prior, .. } => { + // remove any delegation votes to our current target. + Self::reduce_upstream_delegation(&target, conviction.votes(balance)); + voting.set_common(delegations, prior); + } + Voting::Direct { votes, delegations, prior } => { + // here we just ensure that we're currently idling with no votes recorded. + ensure!(votes.is_empty(), Error::::VotesExist); + voting.set_common(delegations, prior); + } + } + Self::increase_upstream_delegation(&target, conviction.votes(balance)); + // Extend the lock to `balance` (rather than setting it) since we don't know what other + // votes are in place. + T::Currency::extend_lock( + DEMOCRACY_ID, + &who, + balance, + WithdrawReason::Transfer.into() + ); + Ok(()) + })?; + Self::deposit_event(Event::::Delegated(who, target)); + Ok(()) + } + + /// Attempt to end the current delegation. + fn try_undelegate(who: T::AccountId) -> DispatchResult { + VotingOf::::try_mutate(&who, |voting| -> DispatchResult { + let mut old = Voting::default(); + sp_std::mem::swap(&mut old, voting); + match old { + 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 = system::Module::::block_number(); + let lock_periods = conviction.lock_periods().into(); + prior.accumulate(now + T::EnactmentPeriod::get() * lock_periods, balance); + voting.set_common(delegations, prior); + } + Voting::Direct { .. } => { + return Err(Error::::NotDelegating.into()) + } + } + Ok(()) + })?; + Self::deposit_event(Event::::Undelegated(who)); + Ok(()) + } + + /// 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(who: &T::AccountId) { + let lock_needed = VotingOf::::mutate(who, |voting| { + voting.rejig(system::Module::::block_number()); + voting.locked_balance() + }); + if lock_needed.is_zero() { + T::Currency::remove_lock(DEMOCRACY_ID, who); + } else { + T::Currency::set_lock(DEMOCRACY_ID, who, lock_needed, WithdrawReason::Transfer.into()); + } + } + + /// Start a referendum + fn inject_referendum( + end: T::BlockNumber, + proposal_hash: T::Hash, + 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() }; + let item = ReferendumInfo::Ongoing(status); + >::insert(ref_index, item); + Self::deposit_event(RawEvent::Started(ref_index, threshold)); + ref_index + } + + /// Enact a proposal from a referendum. + fn enact_proposal(proposal_hash: T::Hash, index: ReferendumIndex) -> DispatchResult { + if let Some((encoded_proposal, who, amount, _)) = >::take(&proposal_hash) { + if let Ok(proposal) = T::Proposal::decode(&mut &encoded_proposal[..]) { + let _ = T::Currency::unreserve(&who, amount); + Self::deposit_event(RawEvent::PreimageUsed(proposal_hash, who, amount)); + + let ok = proposal.dispatch(frame_system::RawOrigin::Root.into()).is_ok(); + Self::deposit_event(RawEvent::Executed(index, ok)); + + Ok(()) + } else { + T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); + Self::deposit_event(RawEvent::PreimageInvalid(proposal_hash, index)); + Err(Error::::PreimageInvalid.into()) + } + } else { + Self::deposit_event(RawEvent::PreimageMissing(proposal_hash, index)); + Err(Error::::PreimageMissing.into()) + } + } + + /// Table the next waiting proposal for a vote. + fn launch_next(now: T::BlockNumber) -> DispatchResult { if LastTabledWasExternal::take() { Self::launch_public(now).or_else(|_| Self::launch_external(now)) } else { @@ -1472,7 +1597,7 @@ impl Module { let mut public_props = Self::public_props(); if let Some((winner_index, _)) = public_props.iter() .enumerate() - .max_by_key(|x| Self::locked_for((x.1).0).unwrap_or_else(Zero::zero) + .max_by_key(|x| Self::backing_for((x.1).0).unwrap_or_else(Zero::zero) /* ^^ defensive only: All current public proposals have an amount locked*/) { let (prop_index, proposal, _) = public_props.swap_remove(winner_index); @@ -1495,51 +1620,22 @@ impl Module { } else { Err(Error::::NoneWaiting)? } - } fn bake_referendum( now: T::BlockNumber, index: ReferendumIndex, - info: ReferendumInfo - ) -> DispatchResult { - let (approve, against, capital) = Self::tally(index); + status: ReferendumStatus>, + ) -> Result { let total_issuance = T::Currency::total_issuance(); - let approved = info.threshold.approved(approve, against, capital, total_issuance); - let enactment_period = T::EnactmentPeriod::get(); - - // Logic defined in https://www.slideshare.net/gavofyork/governance-in-polkadot-poc3 - // Essentially, we extend the lock-period of the coins behind the winning votes to be the - // vote strength times the public delay period from now. - for (a, lock_periods) in Self::voters_for(index).into_iter() - .map(|a| (a.clone(), Self::vote_of((index, a)))) - // ^^^ defensive only: all items come from `voters`; for an item to be in `voters` - // there must be a vote registered; qed - .filter(|&(_, vote)| vote.aye == approved) // Just the winning coins - .map(|(a, vote)| (a, vote.conviction.lock_periods())) - .filter(|&(_, lock_periods)| !lock_periods.is_zero()) // Just the lock votes - { - // now plus: the base lock period multiplied by the number of periods this voter - // offered to lock should they win... - let locked_until = now + enactment_period * lock_periods.into(); - Locks::::insert(&a, locked_until); - // ...extend their bondage until at least then. - T::Currency::extend_lock( - DEMOCRACY_ID, - &a, - Bounded::max_value(), - WithdrawReason::Transfer.into() - ); - } - - Self::clear_referendum(index); + let approved = status.threshold.approved(status.tally, total_issuance); if approved { Self::deposit_event(RawEvent::Passed(index)); - if info.delay.is_zero() { - let _ = Self::enact_proposal(info.proposal_hash, index); + if status.delay.is_zero() { + let _ = Self::enact_proposal(status.proposal_hash, index); } else { - let item = (now + info.delay,info.proposal_hash, index); + let item = (now + status.delay, status.proposal_hash, index); >::mutate(|queue| { let pos = queue.binary_search_by_key(&item.0, |x| x.0).unwrap_or_else(|e| e); queue.insert(pos, item); @@ -1549,7 +1645,7 @@ impl Module { Self::deposit_event(RawEvent::NotPassed(index)); } - Ok(()) + Ok(approved) } /// Current era is ending; we should finish up any proposals. @@ -1563,7 +1659,8 @@ impl Module { // tally up votes for any expiring referenda. for (index, info) in Self::maturing_referenda_at(now).into_iter() { - Self::bake_referendum(now, index, info)?; + let approved = Self::bake_referendum(now, index, info)?; + ReferendumInfoOf::::insert(index, ReferendumInfo::Finished { end: now, approved }); } let queue = >::get(); @@ -1579,1370 +1676,3 @@ impl Module { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - use std::cell::RefCell; - use frame_support::{ - impl_outer_origin, impl_outer_dispatch, assert_noop, assert_ok, parameter_types, - ord_parameter_types, traits::Contains, weights::Weight, - }; - use sp_core::H256; - use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup, Bounded, BadOrigin}, - testing::Header, Perbill, - }; - use pallet_balances::{BalanceLock, Error as BalancesError}; - use frame_system::EnsureSignedBy; - - const AYE: Vote = Vote{ aye: true, conviction: Conviction::None }; - 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 }; - - impl_outer_origin! { - pub enum Origin for Test where system = frame_system {} - } - - impl_outer_dispatch! { - pub enum Call for Test where origin: Origin { - pallet_balances::Balances, - democracy::Democracy, - } - } - - // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. - #[derive(Clone, Eq, PartialEq, Debug)] - pub struct Test; - 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::Trait for Test { - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Call = (); - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - type BlockHashCount = BlockHashCount; - type MaximumBlockWeight = MaximumBlockWeight; - type MaximumBlockLength = MaximumBlockLength; - type AvailableBlockRatio = AvailableBlockRatio; - type Version = (); - type ModuleToIndex = (); - type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); - type OnKilledAccount = (); - } - parameter_types! { - pub const ExistentialDeposit: u64 = 1; - } - impl pallet_balances::Trait for Test { - type Balance = u64; - type Event = (); - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - } - parameter_types! { - pub const LaunchPeriod: u64 = 2; - pub const VotingPeriod: u64 = 2; - pub const EmergencyVotingPeriod: u64 = 1; - pub const MinimumDeposit: u64 = 1; - pub const EnactmentPeriod: u64 = 2; - pub const CooloffPeriod: u64 = 2; - } - 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 struct OneToFive; - impl Contains for OneToFive { - fn sorted_members() -> Vec { - vec![1, 2, 3, 4, 5] - } - } - thread_local! { - static PREIMAGE_BYTE_DEPOSIT: RefCell = RefCell::new(0); - } - pub struct PreimageByteDeposit; - impl Get for PreimageByteDeposit { - fn get() -> u64 { PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow()) } - } - impl super::Trait for Test { - type Proposal = Call; - type Event = (); - type Currency = pallet_balances::Module; - type EnactmentPeriod = EnactmentPeriod; - type LaunchPeriod = LaunchPeriod; - type VotingPeriod = VotingPeriod; - type EmergencyVotingPeriod = EmergencyVotingPeriod; - type MinimumDeposit = MinimumDeposit; - type ExternalOrigin = EnsureSignedBy; - type ExternalMajorityOrigin = EnsureSignedBy; - type ExternalDefaultOrigin = EnsureSignedBy; - type FastTrackOrigin = EnsureSignedBy; - type CancellationOrigin = EnsureSignedBy; - type VetoOrigin = EnsureSignedBy; - type CooloffPeriod = CooloffPeriod; - type PreimageByteDeposit = PreimageByteDeposit; - type Slash = (); - } - - 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(); - GenesisConfig::default().assimilate_storage(&mut t).unwrap(); - sp_io::TestExternalities::new(t) - } - - type System = frame_system::Module; - type Balances = pallet_balances::Module; - type Democracy = Module; - - #[test] - fn params_should_work() { - new_test_ext().execute_with(|| { - assert_eq!(Democracy::referendum_count(), 0); - assert_eq!(Balances::free_balance(42), 0); - assert_eq!(Balances::total_issuance(), 210); - }); - } - - fn set_balance_proposal(value: u64) -> Vec { - Call::Balances(pallet_balances::Call::set_balance(42, value, 0)).encode() - } - - 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 - ) - } - - fn next_block() { - System::set_block_number(System::block_number() + 1); - assert_eq!(Democracy::begin_block(System::block_number()), Ok(())); - } - - fn fast_forward_to(n: u64) { - while System::block_number() < n { - next_block(); - } - } - - #[test] - fn missing_preimage_should_fail() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - - 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(|| { - System::set_block_number(1); - // fee of 100 is too much. - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); - assert_noop!( - 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)); - - 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(|| { - System::set_block_number(1); - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(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)), - Error::::Early - ); - next_block(); - assert_ok!(Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2))); - - 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(|| { - System::set_block_number(1); - assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), - Error::::PreimageMissing - ); - - PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); - assert_ok!(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)), - Error::::Early - ); - - next_block(); - assert_ok!(Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2))); - 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(|| { - System::set_block_number(1); - 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)); - - assert_noop!( - Democracy::note_imminent_preimage(Origin::signed(7), 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(7), 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(|| { - System::set_block_number(1); - 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)); - next_block(); - next_block(); - // now imminent. - assert_noop!(Democracy::reap_preimage(Origin::signed(6), h), Error::::Imminent); - }); - } - - #[test] - 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)); - - fast_forward_to(2); - - // both waiting: external goes first. - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 4, - proposal_hash: set_balance_proposal_hash_and_note(1), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); - - fast_forward_to(4); - - // both waiting: public goes next. - assert_eq!( - Democracy::referendum_info(1), - Some(ReferendumInfo { - end: 6, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - // don't replenish public - - fast_forward_to(6); - - // it's external "turn" again, though since public is empty that doesn't really matter - assert_eq!( - Democracy::referendum_info(2), - Some(ReferendumInfo { - end: 8, - proposal_hash: set_balance_proposal_hash_and_note(3), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - // replenish external - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(5), - )); - - fast_forward_to(8); - - // external goes again because there's no public waiting. - assert_eq!( - Democracy::referendum_info(3), - Some(ReferendumInfo { - end: 10, - proposal_hash: set_balance_proposal_hash_and_note(5), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - // 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)); - - fast_forward_to(10); - - // public goes now since external went last time. - assert_eq!( - Democracy::referendum_info(4), - Some(ReferendumInfo { - end: 12, - proposal_hash: set_balance_proposal_hash_and_note(4), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - // replenish public again - assert_ok!(propose_set_balance_and_note(6, 6, 2)); - // cancel external - let h = set_balance_proposal_hash_and_note(7); - assert_ok!(Democracy::veto_external(Origin::signed(3), h)); - - fast_forward_to(12); - - // public goes again now since there's no external waiting. - assert_eq!( - Democracy::referendum_info(5), - Some(ReferendumInfo { - end: 14, - proposal_hash: set_balance_proposal_hash_and_note(6), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - }); - } - - - #[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), - VoteThreshold::SuperMajorityApprove, - 2 - ); - assert!(Democracy::referendum_info(r).is_some()); - - assert_noop!(Democracy::emergency_cancel(Origin::signed(3), r), BadOrigin); - assert_ok!(Democracy::emergency_cancel(Origin::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), - VoteThreshold::SuperMajorityApprove, - 2 - ); - assert!(Democracy::referendum_info(r).is_some()); - assert_noop!(Democracy::emergency_cancel(Origin::signed(4), r), Error::::AlreadyCanceled); - }); - } - - #[test] - 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!(>::exists()); - - let h = set_balance_proposal_hash_and_note(2); - assert_ok!(Democracy::veto_external(Origin::signed(3), h.clone())); - // cancelled. - assert!(!>::exists()); - // fails - same proposal can't be resubmitted. - assert_noop!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash(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), - ), 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!(>::exists()); - - // 3 can't veto the same thing twice. - assert_noop!( - Democracy::veto_external(Origin::signed(3), h.clone()), - Error::::AlreadyVetoed - ); - - // 4 vetoes. - assert_ok!(Democracy::veto_external(Origin::signed(4), h.clone())); - // 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), - ), Error::::ProposalBlacklisted); - // different proposal works fine. - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(3), - )); - }); - } - - #[test] - 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), - ), - BadOrigin, - ); - assert_ok!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash_and_note(2), - )); - assert_noop!(Democracy::external_propose( - Origin::signed(2), - set_balance_proposal_hash(1), - ), Error::::DuplicateProposal); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - }); - } - - #[test] - 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) - ), - BadOrigin, - ); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SimpleMajority, - delay: 2, - }) - ); - }); - } - - #[test] - 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) - ), - BadOrigin, - ); - assert_ok!(Democracy::external_propose_default( - Origin::signed(1), - set_balance_proposal_hash_and_note(2) - )); - fast_forward_to(2); - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 4, - proposal_hash: set_balance_proposal_hash(2), - threshold: VoteThreshold::SuperMajorityAgainst, - delay: 2, - }) - ); - }); - } - - #[test] - fn fast_track_referendum_works() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - let h = set_balance_proposal_hash_and_note(2); - assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::::ProposalMissing); - assert_ok!(Democracy::external_propose_majority( - Origin::signed(3), - set_balance_proposal_hash_and_note(2) - )); - assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin); - assert_ok!(Democracy::fast_track(Origin::signed(5), h, 0, 0)); - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 1, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SimpleMajority, - delay: 0, - }) - ); - }); - } - - #[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) - )); - assert_noop!( - Democracy::fast_track(Origin::signed(5), h, 3, 2), - Error::::NotSimpleMajority - ); - }); - } - - #[test] - fn locked_for_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - 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_eq!(Democracy::locked_for(0), Some(2)); - assert_eq!(Democracy::locked_for(1), Some(4)); - assert_eq!(Democracy::locked_for(2), Some(3)); - }); - } - - #[test] - 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!(Democracy::referendum_info(0).is_none()); - - // start of 2 => next referendum scheduled. - fast_forward_to(2); - - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - - assert_eq!(Democracy::referendum_count(), 1); - assert_eq!( - Democracy::referendum_info(0), - Some(ReferendumInfo { - end: 4, - proposal_hash: set_balance_proposal_hash_and_note(2), - threshold: VoteThreshold::SuperMajorityApprove, - delay: 2 - }) - ); - assert_eq!(Democracy::voters_for(r), vec![1]); - assert_eq!(Democracy::vote_of((r, 1)), AYE); - assert_eq!(Democracy::tally(r), (1, 0, 1)); - - fast_forward_to(3); - - // referendum still running - assert!(Democracy::referendum_info(0).is_some()); - - // referendum runs during 2 and 3, ends @ start of 4. - fast_forward_to(4); - - assert!(Democracy::referendum_info(0).is_none()); - assert_eq!(Democracy::dispatch_queue(), vec![ - (6, set_balance_proposal_hash_and_note(2), 0) - ]); - - // referendum passes and wait another two blocks for enactment. - fast_forward_to(6); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[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)); - - fast_forward_to(4); - - assert_eq!(Democracy::dispatch_queue(), vec![ - (6, set_balance_proposal_hash_and_note(2), 0) - ]); - - assert_noop!(Democracy::cancel_queued(Origin::ROOT, 1), Error::::ProposalMissing); - assert_ok!(Democracy::cancel_queued(Origin::ROOT, 0)); - assert_eq!(Democracy::dispatch_queue(), vec![]); - }); - } - - #[test] - fn proxy_should_work() { - new_test_ext().execute_with(|| { - assert_eq!(Democracy::proxy(10), None); - assert!(System::allow_death(&10)); - - assert_noop!(Democracy::activate_proxy(Origin::signed(1), 10), Error::::NotOpen); - - assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); - assert!(!System::allow_death(&10)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); - - assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::WrongOpen); - assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); - - // Can't set when already set. - assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::AlreadyProxy); - - // But this works because 11 isn't proxying. - assert_ok!(Democracy::open_proxy(Origin::signed(11), 2)); - assert_ok!(Democracy::activate_proxy(Origin::signed(2), 11)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); - assert_eq!(Democracy::proxy(11), Some(ProxyState::Active(2))); - - // 2 cannot fire 1's proxy: - assert_noop!(Democracy::deactivate_proxy(Origin::signed(2), 10), Error::::WrongProxy); - - // 1 deactivates their proxy: - assert_ok!(Democracy::deactivate_proxy(Origin::signed(1), 10)); - assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); - // but the proxy account cannot be killed until the proxy is closed. - assert!(!System::allow_death(&10)); - - // and then 10 closes it completely: - assert_ok!(Democracy::close_proxy(Origin::signed(10))); - assert_eq!(Democracy::proxy(10), None); - assert!(System::allow_death(&10)); - - // 11 just closes without 2's "permission". - assert_ok!(Democracy::close_proxy(Origin::signed(11))); - assert_eq!(Democracy::proxy(11), None); - assert!(System::allow_death(&11)); - }); - } - - #[test] - fn single_proposal_should_work_with_proxy() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - let r = 0; - assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); - assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); - assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, AYE)); - - assert_eq!(Democracy::voters_for(r), vec![1]); - assert_eq!(Democracy::vote_of((r, 1)), AYE); - assert_eq!(Democracy::tally(r), (1, 0, 1)); - - fast_forward_to(6); - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - 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)); - - fast_forward_to(2); - - // Delegate vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); - - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - assert_eq!(Democracy::voters_for(r), vec![1]); - assert_eq!(Democracy::vote_of((r, 1)), AYE); - // Delegated vote is counted. - assert_eq!(Democracy::tally(r), (3, 0, 3)); - - fast_forward_to(6); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - fn single_proposal_should_work_with_cyclic_delegation() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - - // Check behavior with cycle. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); - assert_ok!(Democracy::delegate(Origin::signed(3), 2, Conviction::max_value())); - assert_ok!(Democracy::delegate(Origin::signed(1), 3, Conviction::max_value())); - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - assert_eq!(Democracy::voters_for(r), vec![1]); - - // Delegated vote is counted. - assert_eq!(Democracy::tally(r), (6, 0, 6)); - - fast_forward_to(6); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - /// If transactor already voted, delegated vote is overwritten. - 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)); - - fast_forward_to(2); - - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - // Vote. - assert_ok!(Democracy::vote(Origin::signed(2), r, AYE)); - // Delegate vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); - assert_eq!(Democracy::voters_for(r), vec![1, 2]); - assert_eq!(Democracy::vote_of((r, 1)), AYE); - // Delegated vote is not counted. - assert_eq!(Democracy::tally(r), (3, 0, 3)); - - fast_forward_to(6); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - 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)); - - // Delegate and undelegate vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); - assert_ok!(Democracy::undelegate(Origin::signed(2))); - - fast_forward_to(2); - let r = 0; - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - - assert_eq!(Democracy::referendum_count(), 1); - assert_eq!(Democracy::voters_for(r), vec![1]); - assert_eq!(Democracy::vote_of((r, 1)), AYE); - - // Delegated vote is not counted. - assert_eq!(Democracy::tally(r), (1, 0, 1)); - - fast_forward_to(6); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - /// If transactor voted, delegated vote is overwritten. - fn single_proposal_should_work_with_delegation_and_vote() { - new_test_ext().execute_with(|| { - System::set_block_number(0); - - assert_ok!(propose_set_balance_and_note(1, 2, 1)); - - fast_forward_to(2); - let r = 0; - - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - - // Delegate vote. - assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::max_value())); - - // Vote. - assert_ok!(Democracy::vote(Origin::signed(2), r, AYE)); - - assert_eq!(Democracy::referendum_count(), 1); - assert_eq!(Democracy::voters_for(r), vec![1, 2]); - assert_eq!(Democracy::vote_of((r, 1)), AYE); - - // Delegated vote is not counted. - assert_eq!(Democracy::tally(r), (3, 0, 3)); - - fast_forward_to(6); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - fn deposit_for_proposals_should_be_taken() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_ok!(Democracy::second(Origin::signed(2), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); - assert_eq!(Balances::free_balance(1), 5); - assert_eq!(Balances::free_balance(2), 15); - assert_eq!(Balances::free_balance(5), 35); - }); - } - - #[test] - fn deposit_for_proposals_should_be_returned() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_ok!(Democracy::second(Origin::signed(2), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); - fast_forward_to(3); - assert_eq!(Balances::free_balance(1), 10); - assert_eq!(Balances::free_balance(2), 20); - assert_eq!(Balances::free_balance(5), 50); - }); - } - - #[test] - fn proposal_with_deposit_below_minimum_should_not_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_noop!(propose_set_balance(1, 2, 0), Error::::ValueLow); - }); - } - - #[test] - fn poor_proposer_should_not_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_noop!(propose_set_balance(1, 2, 11), BalancesError::::InsufficientBalance); - }); - } - - #[test] - fn poor_seconder_should_not_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - assert_ok!(propose_set_balance_and_note(2, 2, 11)); - assert_noop!(Democracy::second(Origin::signed(1), 0), BalancesError::::InsufficientBalance); - }); - } - - #[test] - 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)); - fast_forward_to(2); - assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE)); - fast_forward_to(4); - assert_ok!(Democracy::vote(Origin::signed(1), 1, AYE)); - fast_forward_to(6); - assert_ok!(Democracy::vote(Origin::signed(1), 2, AYE)); - }); - } - - #[test] - fn ooo_inject_referendums_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r1 = Democracy::inject_referendum( - 3, - set_balance_proposal_hash_and_note(3), - VoteThreshold::SuperMajorityApprove, - 0 - ); - let r2 = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - - assert_ok!(Democracy::vote(Origin::signed(1), r2, AYE)); - assert_eq!(Democracy::voters_for(r2), vec![1]); - assert_eq!(Democracy::vote_of((r2, 1)), AYE); - assert_eq!(Democracy::tally(r2), (1, 0, 1)); - - next_block(); - assert_eq!(Balances::free_balance(42), 2); - - assert_ok!(Democracy::vote(Origin::signed(1), r1, AYE)); - assert_eq!(Democracy::voters_for(r1), vec![1]); - assert_eq!(Democracy::vote_of((r1, 1)), AYE); - assert_eq!(Democracy::tally(r1), (1, 0, 1)); - - next_block(); - assert_eq!(Balances::free_balance(42), 3); - }); - } - - #[test] - fn simple_passing_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(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)); - - assert_eq!(Democracy::voters_for(r), vec![1]); - assert_eq!(Democracy::vote_of((r, 1)), AYE); - assert_eq!(Democracy::tally(r), (1, 0, 1)); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - fn cancel_referendum_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(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)); - assert_ok!(Democracy::cancel_referendum(Origin::ROOT, r.into())); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); - } - - #[test] - fn simple_failing_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(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, NAY)); - - assert_eq!(Democracy::voters_for(r), vec![1]); - assert_eq!(Democracy::vote_of((r, 1)), NAY); - assert_eq!(Democracy::tally(r), (0, 1, 1)); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); - } - - #[test] - fn controversial_voting_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(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, BIG_AYE)); - assert_ok!(Democracy::vote(Origin::signed(2), r, BIG_NAY)); - assert_ok!(Democracy::vote(Origin::signed(3), r, BIG_NAY)); - assert_ok!(Democracy::vote(Origin::signed(4), r, BIG_AYE)); - assert_ok!(Democracy::vote(Origin::signed(5), r, BIG_NAY)); - assert_ok!(Democracy::vote(Origin::signed(6), r, BIG_AYE)); - - assert_eq!(Democracy::tally(r), (110, 100, 210)); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - fn delayed_enactment_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 1 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, AYE)); - assert_ok!(Democracy::vote(Origin::signed(2), r, AYE)); - assert_ok!(Democracy::vote(Origin::signed(3), r, AYE)); - assert_ok!(Democracy::vote(Origin::signed(4), r, AYE)); - assert_ok!(Democracy::vote(Origin::signed(5), r, AYE)); - assert_ok!(Democracy::vote(Origin::signed(6), r, AYE)); - - assert_eq!(Democracy::tally(r), (21, 0, 21)); - - next_block(); - assert_eq!(Balances::free_balance(42), 0); - - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - fn controversial_low_turnout_voting_should_work() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(5), r, BIG_NAY)); - assert_ok!(Democracy::vote(Origin::signed(6), r, BIG_AYE)); - - assert_eq!(Democracy::tally(r), (60, 50, 110)); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 0); - }); - } - - #[test] - fn passing_low_turnout_voting_should_work() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_eq!(Balances::total_issuance(), 210); - - System::set_block_number(1); - let r = Democracy::inject_referendum( - 2, - set_balance_proposal_hash_and_note(2), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(4), r, BIG_AYE)); - assert_ok!(Democracy::vote(Origin::signed(5), r, BIG_NAY)); - assert_ok!(Democracy::vote(Origin::signed(6), r, BIG_AYE)); - - assert_eq!(Democracy::tally(r), (100, 50, 150)); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); - } - - #[test] - fn lock_voting_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), - VoteThreshold::SuperMajorityApprove, - 0 - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, Vote { - aye: false, - conviction: Conviction::Locked5x - })); - assert_ok!(Democracy::vote(Origin::signed(2), r, Vote { - aye: true, - conviction: Conviction::Locked4x - })); - assert_ok!(Democracy::vote(Origin::signed(3), r, Vote { - aye: true, - conviction: Conviction::Locked3x - })); - assert_ok!(Democracy::vote(Origin::signed(4), r, Vote { - aye: true, - conviction: Conviction::Locked2x - })); - assert_ok!(Democracy::vote(Origin::signed(5), r, Vote { - aye: false, - conviction: Conviction::Locked1x - })); - - assert_eq!(Democracy::tally(r), (250, 100, 150)); - - fast_forward_to(2); - - assert_eq!(Balances::locks(1), vec![]); - assert_eq!(Balances::locks(2), vec![BalanceLock { - id: DEMOCRACY_ID, - amount: u64::max_value(), - reasons: pallet_balances::Reasons::Misc, - }]); - assert_eq!(Democracy::locks(2), Some(18)); - assert_eq!(Balances::locks(3), vec![BalanceLock { - id: DEMOCRACY_ID, - amount: u64::max_value(), - reasons: pallet_balances::Reasons::Misc, - }]); - assert_eq!(Democracy::locks(3), Some(10)); - assert_eq!(Balances::locks(4), vec![BalanceLock { - id: DEMOCRACY_ID, - amount: u64::max_value(), - reasons: pallet_balances::Reasons::Misc, - }]); - assert_eq!(Democracy::locks(4), Some(6)); - assert_eq!(Balances::locks(5), vec![]); - - assert_eq!(Balances::free_balance(42), 2); - - assert_noop!(Democracy::unlock(Origin::signed(1), 1), Error::::NotLocked); - - fast_forward_to(5); - assert_noop!(Democracy::unlock(Origin::signed(1), 4), Error::::NotExpired); - fast_forward_to(6); - assert_ok!(Democracy::unlock(Origin::signed(1), 4)); - assert_noop!(Democracy::unlock(Origin::signed(1), 4), Error::::NotLocked); - - fast_forward_to(9); - assert_noop!(Democracy::unlock(Origin::signed(1), 3), Error::::NotExpired); - fast_forward_to(10); - assert_ok!(Democracy::unlock(Origin::signed(1), 3)); - assert_noop!(Democracy::unlock(Origin::signed(1), 3), Error::::NotLocked); - - fast_forward_to(17); - assert_noop!(Democracy::unlock(Origin::signed(1), 2), Error::::NotExpired); - fast_forward_to(18); - assert_ok!(Democracy::unlock(Origin::signed(1), 2)); - assert_noop!(Democracy::unlock(Origin::signed(1), 2), Error::::NotLocked); - }); - } - - #[test] - fn no_locks_without_conviction_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), - VoteThreshold::SuperMajorityApprove, - 0, - ); - assert_ok!(Democracy::vote(Origin::signed(1), r, Vote { - aye: true, - conviction: Conviction::None, - })); - - fast_forward_to(2); - - assert_eq!(Balances::free_balance(42), 2); - assert_eq!(Balances::locks(1), vec![]); - }); - } - - #[test] - fn lock_voting_should_work_with_delegation() { - new_test_ext().execute_with(|| { - System::set_block_number(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, Vote { - aye: false, - conviction: Conviction::Locked5x - })); - assert_ok!(Democracy::vote(Origin::signed(2), r, Vote { - aye: true, - conviction: Conviction::Locked4x - })); - assert_ok!(Democracy::vote(Origin::signed(3), r, Vote { - aye: true, - conviction: Conviction::Locked3x - })); - assert_ok!(Democracy::delegate(Origin::signed(4), 2, Conviction::Locked2x)); - assert_ok!(Democracy::vote(Origin::signed(5), r, Vote { - aye: false, - conviction: Conviction::Locked1x - })); - - assert_eq!(Democracy::tally(r), (250, 100, 150)); - - next_block(); - next_block(); - - assert_eq!(Balances::free_balance(42), 2); - }); - } -} diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..1e2e6dc688277658c4d7a23637d8bd3bfa12b242 --- /dev/null +++ b/frame/democracy/src/tests.rs @@ -0,0 +1,252 @@ +// Copyright 2017-2020 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 . + +//! The crate's tests. + +use super::*; +use std::cell::RefCell; +use codec::Encode; +use frame_support::{ + impl_outer_origin, impl_outer_dispatch, assert_noop, assert_ok, parameter_types, + ord_parameter_types, traits::Contains, weights::Weight, +}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup, BadOrigin}, + testing::Header, Perbill, +}; +use pallet_balances::{BalanceLock, Error as BalancesError}; +use frame_system::EnsureSignedBy; + +mod cancellation; +mod delegation; +mod external_proposing; +mod fast_tracking; +mod lock_voting; +mod preimage; +mod proxying; +mod public_proposals; +mod scheduling; +mod voting; + +const AYE: Vote = Vote { aye: true, conviction: Conviction::None }; +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 }; + +impl_outer_origin! { + pub enum Origin for Test where system = frame_system {} +} + +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + pallet_balances::Balances, + democracy::Democracy, + } +} + +// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct Test; +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::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = (); + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} +impl pallet_balances::Trait for Test { + type Balance = u64; + type Event = (); + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} +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 CooloffPeriod: u64 = 2; +} +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 Contains for OneToFive { + fn sorted_members() -> Vec { + vec![1, 2, 3, 4, 5] + } +} +thread_local! { + static PREIMAGE_BYTE_DEPOSIT: RefCell = RefCell::new(0); + static INSTANT_ALLOWED: RefCell = RefCell::new(false); +} +pub struct PreimageByteDeposit; +impl Get for PreimageByteDeposit { + fn get() -> u64 { PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow()) } +} +pub struct InstantAllowed; +impl Get for InstantAllowed { + fn get() -> bool { INSTANT_ALLOWED.with(|v| *v.borrow()) } +} +impl super::Trait for Test { + type Proposal = Call; + type Event = (); + type Currency = pallet_balances::Module; + type EnactmentPeriod = EnactmentPeriod; + type LaunchPeriod = LaunchPeriod; + type VotingPeriod = VotingPeriod; + type FastTrackVotingPeriod = FastTrackVotingPeriod; + type MinimumDeposit = MinimumDeposit; + type ExternalOrigin = EnsureSignedBy; + type ExternalMajorityOrigin = EnsureSignedBy; + type ExternalDefaultOrigin = EnsureSignedBy; + type FastTrackOrigin = EnsureSignedBy; + type CancellationOrigin = EnsureSignedBy; + type VetoOrigin = EnsureSignedBy; + type CooloffPeriod = CooloffPeriod; + type PreimageByteDeposit = PreimageByteDeposit; + type Slash = (); + type InstantOrigin = EnsureSignedBy; + type InstantAllowed = InstantAllowed; +} + +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(); + GenesisConfig::default().assimilate_storage(&mut t).unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +type System = frame_system::Module; +type Balances = pallet_balances::Module; +type Democracy = Module; + +#[test] +fn params_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Democracy::referendum_count(), 0); + assert_eq!(Balances::free_balance(42), 0); + assert_eq!(Balances::total_issuance(), 210); + }); +} + +fn set_balance_proposal(value: u64) -> Vec { + Call::Balances(pallet_balances::Call::set_balance(42, value, 0)).encode() +} + +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 + ) +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); + assert_eq!(Democracy::begin_block(System::block_number()), Ok(())); +} + +fn fast_forward_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +fn begin_referendum() -> ReferendumIndex { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + fast_forward_to(2); + 0 +} + +fn aye(who: u64) -> AccountVote { + AccountVote::Standard { vote: AYE, balance: Balances::free_balance(&who) } +} + +fn nay(who: u64) -> AccountVote { + AccountVote::Standard { vote: NAY, balance: Balances::free_balance(&who) } +} + +fn big_aye(who: u64) -> AccountVote { + AccountVote::Standard { vote: BIG_AYE, balance: Balances::free_balance(&who) } +} + +fn big_nay(who: u64) -> AccountVote { + AccountVote::Standard { vote: BIG_NAY, balance: Balances::free_balance(&who) } +} + +fn tally(r: ReferendumIndex) -> Tally { + Democracy::referendum_status(r).unwrap().tally +} diff --git a/frame/democracy/src/tests/cancellation.rs b/frame/democracy/src/tests/cancellation.rs new file mode 100644 index 0000000000000000000000000000000000000000..424ec36dbeafca09785cf266b50898d13fe04451 --- /dev/null +++ b/frame/democracy/src/tests/cancellation.rs @@ -0,0 +1,93 @@ +// Copyright 2017-2020 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 . + +//! The tests for cancelation functionality. + +use super::*; + +#[test] +fn cancel_referendum_should_work() { + new_test_ext().execute_with(|| { + 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_ok!(Democracy::cancel_referendum(Origin::ROOT, r.into())); + + next_block(); + next_block(); + + 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_eq!(Democracy::dispatch_queue(), vec![ + (6, set_balance_proposal_hash_and_note(2), 0) + ]); + + assert_noop!(Democracy::cancel_queued(Origin::ROOT, 1), Error::::ProposalMissing); + assert_ok!(Democracy::cancel_queued(Origin::ROOT, 0)); + assert_eq!(Democracy::dispatch_queue(), vec![]); + }); +} + +#[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), + 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!(Democracy::referendum_info(r).is_none()); + + // some time later... + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 2 + ); + assert!(Democracy::referendum_status(r).is_ok()); + assert_noop!( + Democracy::emergency_cancel(Origin::signed(4), r), + Error::::AlreadyCanceled, + ); + }); +} diff --git a/frame/democracy/src/tests/delegation.rs b/frame/democracy/src/tests/delegation.rs new file mode 100644 index 0000000000000000000000000000000000000000..061a48b587798219f5562e8ca13fadd214c4cfe8 --- /dev/null +++ b/frame/democracy/src/tests/delegation.rs @@ -0,0 +1,178 @@ +// Copyright 2017-2020 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 . + +//! The tests for functionality concerning delegation. + +use super::*; + +#[test] +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)); + + fast_forward_to(2); + + // Delegate first vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + let r = 0; + assert_ok!(Democracy::vote(Origin::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_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_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_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_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_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_eq!(tally(r), Tally { ayes: 11, nays: 0, turnout: 20 }); + }); +} + +#[test] +fn self_delegation_not_allowed() { + new_test_ext().execute_with(|| { + assert_noop!( + Democracy::delegate(Origin::signed(1), 1, Conviction::None, 10), + Error::::Nonsense, + ); + }); +} + +#[test] +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)); + + 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)); + 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))); + + // Delegated vote is counted. + assert_eq!(tally(r), Tally { ayes: 3, nays: 3, turnout: 60 }); + }); +} + +#[test] +fn single_proposal_should_work_with_vote_and_delegation() { + // If transactor already voted, delegated vote is overwritten. + new_test_ext().execute_with(|| { + System::set_block_number(0); + + assert_ok!(propose_set_balance_and_note(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_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)); + // Delegated vote replaces the explicit vote. + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + }); +} + +#[test] +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)); + + // Delegate and undelegate vote. + assert_ok!(Democracy::delegate(Origin::signed(2), 1, Conviction::None, 20)); + assert_ok!(Democracy::undelegate(Origin::signed(2))); + + fast_forward_to(2); + let r = 0; + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); + + // Delegated vote is not counted. + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + }); +} + +#[test] +fn single_proposal_should_work_with_delegation_and_vote() { + // If transactor voted, delegated vote is overwritten. + 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_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))); + // Delegated vote is not counted. + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + }); +} + +#[test] +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))); + // Delegated vote is huge. + assert_eq!(tally(r), Tally { ayes: 121, nays: 0, turnout: 30 }); + }); +} + +#[test] +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 })); + // Delegated vote is huge. + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + }); +} diff --git a/frame/democracy/src/tests/external_proposing.rs b/frame/democracy/src/tests/external_proposing.rs new file mode 100644 index 0000000000000000000000000000000000000000..a249a806ee9edf4af87b6ecc81920783ac29c431 --- /dev/null +++ b/frame/democracy/src/tests/external_proposing.rs @@ -0,0 +1,289 @@ +// Copyright 2017-2020 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 . + +//! The tests for functionality concerning the "external" origin. + +use super::*; + +#[test] +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!(>::exists()); + + let h = set_balance_proposal_hash_and_note(2); + assert_ok!(Democracy::veto_external(Origin::signed(3), h.clone())); + // cancelled. + assert!(!>::exists()); + // fails - same proposal can't be resubmitted. + assert_noop!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash(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), + ), 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!(>::exists()); + + // 3 can't veto the same thing twice. + assert_noop!( + Democracy::veto_external(Origin::signed(3), h.clone()), + Error::::AlreadyVetoed + ); + + // 4 vetoes. + assert_ok!(Democracy::veto_external(Origin::signed(4), h.clone())); + // 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), + ), Error::::ProposalBlacklisted); + // different proposal works fine. + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(3), + )); + }); +} + +#[test] +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), + ), + BadOrigin, + ); + assert_ok!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash_and_note(2), + )); + assert_noop!(Democracy::external_propose( + Origin::signed(2), + set_balance_proposal_hash(1), + ), Error::::DuplicateProposal); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +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) + ), + BadOrigin, + ); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(2) + )); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SimpleMajority, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +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) + ), + BadOrigin, + ); + assert_ok!(Democracy::external_propose_default( + Origin::signed(1), + set_balance_proposal_hash_and_note(2) + )); + fast_forward_to(2); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash(2), + threshold: VoteThreshold::SuperMajorityAgainst, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + + +#[test] +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)); + + fast_forward_to(2); + + // both waiting: external goes first. + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash_and_note(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), + )); + + fast_forward_to(4); + + // both waiting: public goes next. + assert_eq!( + Democracy::referendum_status(1), + Ok(ReferendumStatus { + end: 6, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + // don't replenish public + + fast_forward_to(6); + + // it's external "turn" again, though since public is empty that doesn't really matter + assert_eq!( + Democracy::referendum_status(2), + Ok(ReferendumStatus { + end: 8, + proposal_hash: set_balance_proposal_hash_and_note(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), + )); + + fast_forward_to(8); + + // external goes again because there's no public waiting. + assert_eq!( + Democracy::referendum_status(3), + Ok(ReferendumStatus { + end: 10, + proposal_hash: set_balance_proposal_hash_and_note(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)); + + fast_forward_to(10); + + // public goes now since external went last time. + assert_eq!( + Democracy::referendum_status(4), + Ok(ReferendumStatus { + end: 12, + proposal_hash: set_balance_proposal_hash_and_note(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)); + // cancel external + let h = set_balance_proposal_hash_and_note(7); + assert_ok!(Democracy::veto_external(Origin::signed(3), h)); + + fast_forward_to(12); + + // public goes again now since there's no external waiting. + assert_eq!( + Democracy::referendum_status(5), + Ok(ReferendumStatus { + end: 14, + proposal_hash: set_balance_proposal_hash_and_note(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 new file mode 100644 index 0000000000000000000000000000000000000000..5ce9b15baf34cde31d2788679f3bdb106e1e8a84 --- /dev/null +++ b/frame/democracy/src/tests/fast_tracking.rs @@ -0,0 +1,88 @@ +// Copyright 2017-2020 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 . + +//! The tests for fast-tracking functionality. + +use super::*; + +#[test] +fn fast_track_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::::ProposalMissing); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(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_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 2, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SimpleMajority, + delay: 0, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[test] +fn instant_referendum_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let h = set_balance_proposal_hash_and_note(2); + assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::::ProposalMissing); + assert_ok!(Democracy::external_propose_majority( + Origin::signed(3), + set_balance_proposal_hash_and_note(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(Origin::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_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 1, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SimpleMajority, + delay: 0, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + }); +} + +#[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) + )); + assert_noop!( + Democracy::fast_track(Origin::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 new file mode 100644 index 0000000000000000000000000000000000000000..e83d974a8dc27fc990950ed1ae3895946b643e03 --- /dev/null +++ b/frame/democracy/src/tests/lock_voting.rs @@ -0,0 +1,363 @@ +// Copyright 2017-2020 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 . + +//! The tests for functionality concerning locking and lock-voting. + +use super::*; +use std::convert::TryFrom; + +fn aye(x: u8, balance: u64) -> AccountVote { + AccountVote::Standard { + vote: Vote { aye: true, conviction: Conviction::try_from(x).unwrap() }, + balance + } +} + +fn nay(x: u8, balance: u64) -> AccountVote { + AccountVote::Standard { + vote: Vote { aye: false, conviction: Conviction::try_from(x).unwrap() }, + balance + } +} + +fn the_lock(amount: u64) -> BalanceLock { + BalanceLock { + id: DEMOCRACY_ID, + amount, + reasons: pallet_balances::Reasons::Misc, + } +} + +#[test] +fn lock_voting_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), + 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_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); + + // All balances are currently locked. + for i in 1..=5 { + assert_eq!(Balances::locks(i), vec![the_lock(i * 10)]); + } + + fast_forward_to(2); + + // 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)); + // 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)); + + // 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), 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_eq!(Balances::locks(1), vec![]); + assert_eq!(Balances::locks(2), vec![the_lock(20)]); + assert_eq!(Balances::locks(3), vec![the_lock(30)]); + assert_eq!(Balances::locks(4), vec![the_lock(40)]); + assert_eq!(Balances::locks(5), vec![]); + assert_eq!(Balances::free_balance(42), 2); + + + fast_forward_to(5); + // No change yet... + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 4, r), Error::::NoPermission); + assert_ok!(Democracy::unlock(Origin::signed(1), 4)); + assert_eq!(Balances::locks(4), vec![the_lock(40)]); + fast_forward_to(6); + // 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_eq!(Balances::locks(4), vec![]); + + fast_forward_to(9); + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 3, r), Error::::NoPermission); + assert_ok!(Democracy::unlock(Origin::signed(1), 3)); + assert_eq!(Balances::locks(3), vec![the_lock(30)]); + fast_forward_to(10); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 3, r)); + assert_ok!(Democracy::unlock(Origin::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(17); + assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_eq!(Balances::locks(2), vec![the_lock(20)]); + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(1), 2)); + assert_eq!(Balances::locks(2), vec![]); + }); +} + +#[test] +fn no_locks_without_conviction_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), + VoteThreshold::SuperMajorityApprove, + 0, + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, aye(0, 10))); + + fast_forward_to(2); + + 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_eq!(Balances::locks(1), vec![]); + }); +} + +#[test] +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), + 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_eq!(tally(r), Tally { ayes: 250, nays: 100, turnout: 150 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +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))); + + fast_forward_to(2); + + (r1, r2, r3) +} + +#[test] +fn prior_lockvotes_should_be_enforced() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until #18. + // r.1 locked 20 until #10. + // r.2 locked 50 until #6. + + fast_forward_to(5); + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 5, r.2), Error::::NoPermission); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(50)]); + fast_forward_to(6); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.2)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(9); + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 5, r.1), Error::::NoPermission); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(10); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.1)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(17); + assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 5, r.0), Error::::NoPermission); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(18); + assert_ok!(Democracy::remove_other_vote(Origin::signed(1), 5, r.0)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn single_consolidation_of_lockvotes_should_work_as_before() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until #18. + // r.1 locked 20 until #10. + // r.2 locked 50 until #6. + + fast_forward_to(5); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.2)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(50)]); + fast_forward_to(6); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + + fast_forward_to(9); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.1)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(20)]); + fast_forward_to(10); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + + fast_forward_to(17); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r.0)); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![the_lock(10)]); + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn multi_consolidation_of_lockvotes_should_be_conservative() { + new_test_ext().execute_with(|| { + let r = setup_three_referenda(); + // r.0 locked 10 until #18. + // r.1 locked 20 until #10. + // r.2 locked 50 until #6. + + 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)); + + fast_forward_to(6); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 20); + + fast_forward_to(10); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +fn locks_should_persist_from_voting_to_delegation() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SimpleMajority, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(5), r, aye(4, 10))); + fast_forward_to(2); + assert_ok!(Democracy::remove_vote(Origin::signed(5), r)); + // locked 10 until #18. + + assert_ok!(Democracy::delegate(Origin::signed(5), 1, Conviction::Locked3x, 20)); + // locked 20. + assert!(Balances::locks(5)[0].amount == 20); + + assert_ok!(Democracy::undelegate(Origin::signed(5))); + // locked 20 until #10 + + fast_forward_to(9); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount == 20); + + fast_forward_to(10); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(17); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +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))); + // locked 5 until #32 + + let r = setup_three_referenda(); + // r.0 locked 10 until #18. + // r.1 locked 20 until #10. + // r.2 locked 50 until #6. + + 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)); + + fast_forward_to(6); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 20); + + fast_forward_to(10); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 10); + + fast_forward_to(18); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert!(Balances::locks(5)[0].amount >= 5); + + fast_forward_to(32); + assert_ok!(Democracy::unlock(Origin::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 new file mode 100644 index 0000000000000000000000000000000000000000..8d834c319e3c3316baaa627b2d11dfeef1f008ae --- /dev/null +++ b/frame/democracy/src/tests/preimage.rs @@ -0,0 +1,158 @@ +// Copyright 2017-2020 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 . + +//! 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(|| { + // fee of 100 is too much. + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100); + assert_noop!( + 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(|| { + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + assert_ok!(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)), + Error::::TooEarly + ); + next_block(); + assert_ok!(Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2))); + + 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(|| { + assert_noop!( + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), + Error::::PreimageMissing + ); + + PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1); + assert_ok!(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)), + Error::::TooEarly + ); + + next_block(); + assert_ok!(Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2))); + 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(|| { + 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!( + Democracy::note_imminent_preimage(Origin::signed(7), 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(7), 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(); + // now imminent. + assert_noop!(Democracy::reap_preimage(Origin::signed(6), h), Error::::Imminent); + }); +} diff --git a/frame/democracy/src/tests/proxying.rs b/frame/democracy/src/tests/proxying.rs new file mode 100644 index 0000000000000000000000000000000000000000..412adf6be03e707a22e9cd3bb03df3c8f2e6c6cd --- /dev/null +++ b/frame/democracy/src/tests/proxying.rs @@ -0,0 +1,104 @@ +// Copyright 2017-2020 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 . + +//! The tests for functionality concerning proxying. + +use super::*; + +#[test] +fn proxy_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Democracy::proxy(10), None); + assert!(System::allow_death(&10)); + + assert_noop!(Democracy::activate_proxy(Origin::signed(1), 10), Error::::NotOpen); + + assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); + assert!(!System::allow_death(&10)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); + + assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::WrongOpen); + assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); + + // Can't set when already set. + assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::::AlreadyProxy); + + // But this works because 11 isn't proxying. + assert_ok!(Democracy::open_proxy(Origin::signed(11), 2)); + assert_ok!(Democracy::activate_proxy(Origin::signed(2), 11)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1))); + assert_eq!(Democracy::proxy(11), Some(ProxyState::Active(2))); + + // 2 cannot fire 1's proxy: + assert_noop!(Democracy::deactivate_proxy(Origin::signed(2), 10), Error::::WrongProxy); + + // 1 deactivates their proxy: + assert_ok!(Democracy::deactivate_proxy(Origin::signed(1), 10)); + assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1))); + // but the proxy account cannot be killed until the proxy is closed. + assert!(!System::allow_death(&10)); + + // and then 10 closes it completely: + assert_ok!(Democracy::close_proxy(Origin::signed(10))); + assert_eq!(Democracy::proxy(10), None); + assert!(System::allow_death(&10)); + + // 11 just closes without 2's "permission". + assert_ok!(Democracy::close_proxy(Origin::signed(11))); + assert_eq!(Democracy::proxy(11), None); + assert!(System::allow_death(&11)); + }); +} + +#[test] +fn voting_and_removing_votes_should_work_with_proxy() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + + fast_forward_to(2); + let r = 0; + assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); + assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); + + assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, aye(1))); + assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + + assert_ok!(Democracy::proxy_remove_vote(Origin::signed(10), r)); + assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + }); +} + +#[test] +fn delegation_and_undelegation_should_work_with_proxy() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert_ok!(propose_set_balance_and_note(1, 2, 1)); + fast_forward_to(2); + let r = 0; + assert_ok!(Democracy::open_proxy(Origin::signed(10), 1)); + assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10)); + assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2))); + + assert_ok!(Democracy::proxy_delegate(Origin::signed(10), 2, Conviction::None, 10)); + assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 }); + + assert_ok!(Democracy::proxy_undelegate(Origin::signed(10))); + assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 20 }); + }); +} + diff --git a/frame/democracy/src/tests/public_proposals.rs b/frame/democracy/src/tests/public_proposals.rs new file mode 100644 index 0000000000000000000000000000000000000000..04246e86f1da4fb530fb47a58bb9fbb07120bc43 --- /dev/null +++ b/frame/democracy/src/tests/public_proposals.rs @@ -0,0 +1,98 @@ +// Copyright 2017-2020 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 . + +//! The tests for the public proposal queue. + +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_eq!(Democracy::backing_for(0), Some(2)); + assert_eq!(Democracy::backing_for(1), Some(4)); + assert_eq!(Democracy::backing_for(2), Some(3)); + }); +} + +#[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)); + assert_ok!(Democracy::second(Origin::signed(5), 0)); + assert_ok!(Democracy::second(Origin::signed(5), 0)); + assert_ok!(Democracy::second(Origin::signed(5), 0)); + assert_eq!(Balances::free_balance(1), 5); + assert_eq!(Balances::free_balance(2), 15); + assert_eq!(Balances::free_balance(5), 35); + }); +} + +#[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)); + assert_ok!(Democracy::second(Origin::signed(5), 0)); + assert_ok!(Democracy::second(Origin::signed(5), 0)); + assert_ok!(Democracy::second(Origin::signed(5), 0)); + fast_forward_to(3); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 20); + assert_eq!(Balances::free_balance(5), 50); + }); +} + +#[test] +fn proposal_with_deposit_below_minimum_should_not_work() { + new_test_ext().execute_with(|| { + assert_noop!(propose_set_balance(1, 2, 0), Error::::ValueLow); + }); +} + +#[test] +fn poor_proposer_should_not_work() { + new_test_ext().execute_with(|| { + assert_noop!(propose_set_balance(1, 2, 11), BalancesError::::InsufficientBalance); + }); +} + +#[test] +fn poor_seconder_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(2, 2, 11)); + assert_noop!(Democracy::second(Origin::signed(1), 0), BalancesError::::InsufficientBalance); + }); +} + +#[test] +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)); + fast_forward_to(2); + assert_ok!(Democracy::vote(Origin::signed(1), 0, aye(1))); + fast_forward_to(4); + assert_ok!(Democracy::vote(Origin::signed(1), 1, aye(1))); + fast_forward_to(6); + assert_ok!(Democracy::vote(Origin::signed(1), 2, aye(1))); + }); +} diff --git a/frame/democracy/src/tests/scheduling.rs b/frame/democracy/src/tests/scheduling.rs new file mode 100644 index 0000000000000000000000000000000000000000..db9724deddc223d8895ce807f91ce65e9c020a8b --- /dev/null +++ b/frame/democracy/src/tests/scheduling.rs @@ -0,0 +1,111 @@ +// Copyright 2017-2020 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 . + +//! The tests for functionality concerning normal starting, ending and enacting of referenda. + +use super::*; + +#[test] +fn simple_passing_should_work() { + new_test_ext().execute_with(|| { + 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!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 }); + next_block(); + next_block(); + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn simple_failing_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1))); + assert_eq!(tally(r), Tally { ayes: 0, nays: 1, turnout: 10 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn ooo_inject_referendums_should_work() { + new_test_ext().execute_with(|| { + let r1 = Democracy::inject_referendum( + 3, + set_balance_proposal_hash_and_note(3), + VoteThreshold::SuperMajorityApprove, + 0 + ); + let r2 = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(2), + VoteThreshold::SuperMajorityApprove, + 0 + ); + + assert_ok!(Democracy::vote(Origin::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_eq!(tally(r1), Tally { ayes: 1, nays: 0, turnout: 10 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 3); + }); +} + +#[test] +fn delayed_enactment_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(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_eq!(tally(r), Tally { ayes: 21, nays: 0, turnout: 210 }); + + next_block(); + assert_eq!(Balances::free_balance(42), 0); + + next_block(); + assert_eq!(Balances::free_balance(42), 2); + }); +} diff --git a/frame/democracy/src/tests/voting.rs b/frame/democracy/src/tests/voting.rs new file mode 100644 index 0000000000000000000000000000000000000000..bdb8edb758ed5217f85cac9eb7d82e88589b4da2 --- /dev/null +++ b/frame/democracy/src/tests/voting.rs @@ -0,0 +1,167 @@ +// Copyright 2017-2020 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 . + +//! The tests for normal voting functionality. + +use super::*; + +#[test] +fn overvoting_should_fail() { + new_test_ext().execute_with(|| { + let r = begin_referendum(); + assert_noop!(Democracy::vote(Origin::signed(1), r, aye(2)), Error::::InsufficientFunds); + }); +} + +#[test] +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); + let v = AccountVote::Split { aye: 30, nay: 20 }; + assert_ok!(Democracy::vote(Origin::signed(5), r, v)); + + assert_eq!(tally(r), Tally { ayes: 3, nays: 2, turnout: 50 }); + }); +} + +#[test] +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_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 }); + assert_ok!(Democracy::unlock(Origin::signed(5), 5)); + assert_eq!(Balances::locks(5), vec![]); + }); +} + +#[test] +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)); + 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_eq!(Democracy::referendum_count(), 1); + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: 4, + proposal_hash: set_balance_proposal_hash_and_note(2), + threshold: VoteThreshold::SuperMajorityApprove, + delay: 2, + tally: Tally { ayes: 1, nays: 0, turnout: 10 }, + }) + ); + + fast_forward_to(3); + + // referendum still running + assert!(Democracy::referendum_status(0).is_ok()); + + // referendum runs during 2 and 3, ends @ start of 4. + fast_forward_to(4); + + assert!(Democracy::referendum_status(0).is_err()); + assert_eq!(Democracy::dispatch_queue(), vec![ + (6, set_balance_proposal_hash_and_note(2), 0) + ]); + + // referendum passes and wait another two blocks for enactment. + fast_forward_to(6); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +fn controversial_voting_should_work() { + new_test_ext().execute_with(|| { + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(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_eq!(tally(r), Tally { ayes: 110, nays: 100, turnout: 210 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 2); + }); +} + +#[test] +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), + 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_eq!(tally(r), Tally { ayes: 60, nays: 50, turnout: 110 }); + + next_block(); + next_block(); + + assert_eq!(Balances::free_balance(42), 0); + }); +} + +#[test] +fn passing_low_turnout_voting_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_eq!(Balances::total_issuance(), 210); + + let r = Democracy::inject_referendum( + 2, + set_balance_proposal_hash_and_note(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_eq!(tally(r), Tally { ayes: 100, nays: 50, turnout: 150 }); + + next_block(); + next_block(); + assert_eq!(Balances::free_balance(42), 2); + }); +} diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs new file mode 100644 index 0000000000000000000000000000000000000000..3454326364de60412048467b8602df796110e3fd --- /dev/null +++ b/frame/democracy/src/types.rs @@ -0,0 +1,224 @@ +// Copyright 2017-2020 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 . + +//! Miscellaneous additional datatypes. + +use codec::{Encode, Decode}; +use sp_runtime::RuntimeDebug; +use sp_runtime::traits::{Zero, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, Saturating}; +use crate::{Vote, VoteThreshold, AccountVote, Conviction}; + +/// Info regarding an ongoing referendum. +#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct Tally { + /// The number of aye votes, expressed in terms of post-conviction lock-vote. + pub (crate) ayes: Balance, + /// The number of nay votes, expressed in terms of post-conviction lock-vote. + pub (crate) nays: Balance, + /// The amount of funds currently expressing its opinion. Pre-conviction. + pub (crate) turnout: Balance, +} + +/// Amount of votes and capital placed in delegation for an account. +#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct Delegations { + /// The number of votes (this is post-conviction). + pub (crate) votes: Balance, + /// The amount of raw capital, used for the turnout. + pub (crate) 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), + } + } +} + +impl< + Balance: From + Zero + Copy + CheckedAdd + CheckedSub + CheckedMul + CheckedDiv + Bounded + + Saturating +> Tally { + /// Create a new tally. + pub fn new( + vote: Vote, + balance: Balance, + ) -> 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 }, + turnout: capital, + } + } + + /// 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); + self.turnout = self.turnout.checked_add(&capital)?; + match vote.aye { + true => 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.turnout = self.turnout.checked_add(&aye.capital)?.checked_add(&nay.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); + self.turnout = self.turnout.checked_sub(&capital)?; + match vote.aye { + true => 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.turnout = self.turnout.checked_sub(&aye.capital)?.checked_sub(&nay.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) -> Option<()> { + self.turnout = self.turnout.saturating_add(delegations.capital); + match approve { + true => self.ayes = self.ayes.saturating_add(delegations.votes), + false => self.nays = self.nays.saturating_add(delegations.votes), + } + Some(()) + } + + /// Decrement some amount of votes. + pub fn reduce(&mut self, approve: bool, delegations: Delegations) -> Option<()> { + self.turnout = self.turnout.saturating_sub(delegations.capital); + match approve { + true => self.ayes = self.ayes.saturating_sub(delegations.votes), + false => self.nays = self.nays.saturating_sub(delegations.votes), + } + Some(()) + } +} + +/// Info regarding an ongoing referendum. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct ReferendumStatus { + /// When voting on this referendum will end. + pub (crate) end: BlockNumber, + /// The hash of the proposal being voted on. + pub (crate) proposal_hash: Hash, + /// The thresholding mechanism to determine whether it passed. + pub (crate) threshold: VoteThreshold, + /// The delay (in blocks) to wait after a successful referendum before deploying. + pub (crate) delay: BlockNumber, + /// The current tally of votes in this referendum. + pub (crate) tally: Tally, +} + +/// Info regarding a referendum, present or past. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub enum ReferendumInfo { + /// Referendum is happening, the arg is the block number at which it will end. + Ongoing(ReferendumStatus), + /// Referendum finished at `end`, and has been `approved` or rejected. + Finished{approved: bool, end: BlockNumber}, +} + +impl ReferendumInfo { + /// Create a new instance. + pub fn new( + end: BlockNumber, + proposal_hash: Hash, + threshold: VoteThreshold, + delay: BlockNumber, + ) -> Self { + let s = ReferendumStatus{ end, proposal_hash, threshold, delay, tally: Tally::default() }; + ReferendumInfo::Ongoing(s) + } +} + +/// State of a proxy voting account. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +pub enum ProxyState { + /// Account is open to becoming a proxy but is not yet assigned. + Open(AccountId), + /// Account is actively being a proxy. + Active(AccountId), +} + +impl ProxyState { + pub (crate) fn as_active(self) -> Option { + match self { + ProxyState::Active(a) => Some(a), + ProxyState::Open(_) => None, + } + } +} + +/// 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/democracy/src/vote.rs b/frame/democracy/src/vote.rs new file mode 100644 index 0000000000000000000000000000000000000000..a41eb342aa12cb1ee862a0119a49f97620a0620d --- /dev/null +++ b/frame/democracy/src/vote.rs @@ -0,0 +1,181 @@ +// Copyright 2017-2020 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 . + +//! The vote datatype. + +use sp_std::{prelude::*, result::Result, convert::TryFrom}; +use codec::{Encode, EncodeLike, Decode, Output, Input}; +use sp_runtime::{RuntimeDebug, traits::{Saturating, Zero}}; +use crate::{Conviction, ReferendumIndex, Delegations}; + +/// A number of lock periods, plus a vote, one way or the other. +#[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)] +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"))?, + }) + } +} + +/// A vote for a referendum of a particular account. +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug)] +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 }, +} + +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, 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), + } + } + + /// 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)] +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(); + } + } +} + +/// An indicator for what an account is doing; it can either be delegating or voting. +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)] +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)>, + /// The total amount of delegations that this account has received. + delegations: Delegations, + /// Any pre-existing locks from past voting/delegating activity. + prior: PriorLock, + }, + /// The account is delegating `balance` of its balance to a `target` account with `conviction`. + Delegating { + balance: Balance, + target: AccountId, + conviction: Conviction, + /// The total amount of delegations that this account has received. + delegations: Delegations, + /// Any pre-existing locks from past voting/delegating activity. + prior: PriorLock, + }, +} + +impl Default for Voting { + fn default() -> Self { + Voting::Direct { + votes: Vec::new(), + delegations: Default::default(), + prior: PriorLock(Zero::zero(), Default::default()), + } + } +} + +impl< + Balance: Saturating + Ord + Zero + Copy, + BlockNumber: Ord + Copy + Zero, + AccountId, +> Voting { + pub fn rejig(&mut self, now: BlockNumber) { + match self { + Voting::Direct { prior, .. } => prior, + Voting::Delegating { prior, .. } => prior, + }.rejig(now); + } + + /// The amount of this account's balance that much currently be locked due to voting. + pub fn locked_balance(&self) -> Balance { + match self { + Voting::Direct { votes, prior, .. } => votes.iter() + .map(|i| i.1.balance()) + .fold(prior.locked(), |a, i| a.max(i)), + Voting::Delegating { balance, .. } => *balance, + } + } + + pub fn set_common(&mut self, + delegations: Delegations, + prior: PriorLock + ) { + let (d, p) = match self { + Voting::Direct { ref mut delegations, ref mut prior, .. } => (delegations, prior), + Voting::Delegating { ref mut delegations, ref mut prior, .. } => (delegations, prior), + }; + *d = delegations; + *p = prior; + } +} diff --git a/frame/democracy/src/vote_threshold.rs b/frame/democracy/src/vote_threshold.rs index 46612af09abc5fd161e4583e31b9c616782f602f..fd976b44001cf189d497ec81c9fba789843e72d0 100644 --- a/frame/democracy/src/vote_threshold.rs +++ b/frame/democracy/src/vote_threshold.rs @@ -21,6 +21,7 @@ use serde::{Serialize, Deserialize}; use codec::{Encode, Decode}; use sp_runtime::traits::{Zero, IntegerSquareRoot}; use sp_std::ops::{Add, Mul, Div, Rem}; +use crate::Tally; /// A means of determining if a vote is past pass threshold. #[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, sp_runtime::RuntimeDebug)] @@ -35,10 +36,9 @@ pub enum VoteThreshold { } pub trait Approved { - /// Given `approve` votes for and `against` votes against from a total electorate size of - /// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the - /// overall outcome is in favor of approval. - fn approved(&self, approve: Balance, against: Balance, voters: Balance, electorate: Balance) -> bool; + /// Given a `tally` of votes and a total size of `electorate`, this returns `true` if the + /// overall outcome is in favor of approval according to `self`'s threshold method. + fn approved(&self, tally: Tally, electorate: Balance) -> bool; } /// Return `true` iff `n1 / d1 < n2 / d2`. `d1` and `d2` may not be zero. @@ -69,23 +69,21 @@ fn compare_rationals + Div + Rem + Mul + Div + Rem + Copy> Approved for VoteThreshold { - /// Given `approve` votes for and `against` votes against from a total electorate size of - /// `electorate` of whom `voters` voted (`electorate - voters` are abstainers) then returns true if the - /// overall outcome is in favor of approval. - /// - /// We assume each *voter* may cast more than one *vote*, hence `voters` is not necessarily equal to - /// `approve + against`. - fn approved(&self, approve: Balance, against: Balance, voters: Balance, electorate: Balance) -> bool { - let sqrt_voters = voters.integer_sqrt(); +impl< + Balance: IntegerSquareRoot + Zero + Ord + Add + + Mul + Div + + Rem + Copy, +> Approved for VoteThreshold { + fn approved(&self, tally: Tally, electorate: Balance) -> bool { + let sqrt_voters = tally.turnout.integer_sqrt(); let sqrt_electorate = electorate.integer_sqrt(); if sqrt_voters.is_zero() { return false; } match *self { VoteThreshold::SuperMajorityApprove => - compare_rationals(against, sqrt_voters, approve, sqrt_electorate), + compare_rationals(tally.nays, sqrt_voters, tally.ayes, sqrt_electorate), VoteThreshold::SuperMajorityAgainst => - compare_rationals(against, sqrt_electorate, approve, sqrt_voters), - VoteThreshold::SimpleMajority => approve > against, + compare_rationals(tally.nays, sqrt_electorate, tally.ayes, sqrt_voters), + VoteThreshold::SimpleMajority => tally.ayes > tally.nays, } } } @@ -96,7 +94,7 @@ mod tests { #[test] fn should_work() { - assert_eq!(VoteThreshold::SuperMajorityApprove.approved(60, 50, 110, 210), false); - assert_eq!(VoteThreshold::SuperMajorityApprove.approved(100, 50, 150, 210), true); + assert!(!VoteThreshold::SuperMajorityApprove.approved(Tally{ayes: 60, nays: 50, turnout: 110}, 210)); + assert!(VoteThreshold::SuperMajorityApprove.approved(Tally{ayes: 100, nays: 50, turnout: 150}, 210)); } } diff --git a/frame/elections-phragmen/Cargo.toml b/frame/elections-phragmen/Cargo.toml index 62ae091e9a41dea8fa039e48d329d571659b3852..1fbfbc20ce0daca99c3a21b3741bb585cd821730 100644 --- a/frame/elections-phragmen/Cargo.toml +++ b/frame/elections-phragmen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-elections-phragmen" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,24 +9,25 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME election pallet for PHRAGMEN" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-phragmen = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/phragmen" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.101", optional = true } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-phragmen = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/phragmen" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "2.0.0-alpha.2", path = "../../primitives/io" } +sp-io = { version = "2.0.0-alpha.5", path = "../../primitives/io" } hex-literal = "0.2.1" -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -substrate-test-utils = { version = "2.0.0-alpha.2", path = "../../test-utils" } -serde = { version = "1.0.101" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +substrate-test-utils = { version = "2.0.0-alpha.5", path = "../../test-utils" } [features] default = ["std"] std = [ + "serde", "codec/std", "frame-support/std", "sp-runtime/std", @@ -34,3 +35,7 @@ std = [ "frame-system/std", "sp-std/std", ] +runtime-benchmarks = ["frame-support/runtime-benchmarks"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index ac85b3047d0ba4e5ca10fedfbda0f24d6075a2e0..28fc66bef95b3db5117816e9a70d680bd933f814 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -87,15 +87,15 @@ use sp_runtime::{ print, DispatchResult, DispatchError, Perbill, traits::{Zero, StaticLookup, Convert}, }; use frame_support::{ - decl_storage, decl_event, ensure, decl_module, decl_error, weights::SimpleDispatchInfo, - storage::{StorageMap, IterableStorageMap}, traits::{ + decl_storage, decl_event, ensure, decl_module, decl_error, + weights::{SimpleDispatchInfo, Weight, WeighData}, storage::{StorageMap, IterableStorageMap}, + traits::{ Currency, Get, LockableCurrency, LockIdentifier, ReservableCurrency, WithdrawReasons, - ChangeMembers, OnUnbalanced, WithdrawReason, Contains, BalanceStatus + ChangeMembers, OnUnbalanced, WithdrawReason, Contains, BalanceStatus, InitializeMembers, } }; -use sp_phragmen::ExtendedBalance; +use sp_phragmen::{build_support_map, ExtendedBalance}; use frame_system::{self as system, ensure_signed, ensure_root}; -use frame_support::traits::MigrateAccount; const MODULE_ID: LockIdentifier = *b"phrelect"; @@ -118,6 +118,9 @@ pub trait Trait: frame_system::Trait { /// What to do when the members change. type ChangeMembers: ChangeMembers; + /// What to do with genesis members + type InitializeMembers: InitializeMembers; + /// Convert a balance into a number used for election calculation. /// This must fit into a `u64` but is allowed to be sensibly lossy. type CurrencyToVote: Convert, u64> + Convert>; @@ -159,14 +162,45 @@ decl_storage! { /// The total number of vote rounds that have happened, excluding the upcoming one. pub ElectionRounds get(fn election_rounds): u32 = Zero::zero(); - /// Votes of a particular voter, with the round index of the votes. - pub VotesOf get(fn votes_of): map hasher(twox_64_concat) T::AccountId => Vec; - /// Locked stake of a voter. - pub StakeOf get(fn stake_of): map hasher(twox_64_concat) T::AccountId => BalanceOf; + /// Votes and locked stake of a particular voter. + pub Voting get(fn voting): map hasher(twox_64_concat) T::AccountId => (BalanceOf, Vec); - /// The present candidate list. Sorted based on account-id. A current member or a runner can - /// never enter this vector and is always implicitly assumed to be a candidate. + /// The present candidate list. Sorted based on account-id. A current member or runner-up + /// can never enter this vector and is always implicitly assumed to be a candidate. pub Candidates get(fn candidates): Vec; + } add_extra_genesis { + config(members): Vec<(T::AccountId, BalanceOf)>; + build(|config: &GenesisConfig| { + let members = config.members.iter().map(|(ref member, ref stake)| { + // make sure they have enough stake + assert!( + T::Currency::free_balance(member) >= *stake, + "Genesis member does not have enough stake", + ); + + // reserve candidacy bond and set as members. + T::Currency::reserve(&member, T::CandidacyBond::get()) + .expect("Genesis member does not have enough balance to be a candidate"); + + // Note: all members will only vote for themselves, hence they must be given exactly + // their own stake as total backing. Any sane election should behave as such. + // Nonetheless, stakes will be updated for term 1 onwards according to the election. + >::append(&[(member.clone(), *stake)]) + .expect("Failed to append genesis members."); + + // set self-votes to make persistent. + >::vote( + T::Origin::from(Some(member.clone()).into()), + vec![member.clone()], + *stake, + ).expect("Genesis member could not vote."); + + member.clone() + }).collect::>(); + + // report genesis members to upstream, if any. + T::InitializeMembers::initialize_members(&members); + }) } } @@ -204,12 +238,34 @@ decl_error! { } } +mod migration { + use super::*; + use frame_support::{migration::{StorageKeyIterator, take_storage_item}, Twox64Concat}; + pub fn migrate() { + for (who, votes) in StorageKeyIterator + ::, Twox64Concat> + ::new(b"PhragmenElection", b"VotesOf") + .drain() + { + if let Some(stake) = take_storage_item::<_, BalanceOf, Twox64Concat>(b"PhragmenElection", b"StakeOf", &who) { + Voting::::insert(who, (stake, votes)); + } + } + } +} + decl_module! { pub struct Module for enum Call where origin: T::Origin { type Error = Error; fn deposit_event() = default; + fn on_runtime_upgrade() -> Weight { + migration::migrate::(); + + SimpleDispatchInfo::default().weigh_data(()) + } + const CandidacyBond: BalanceOf = T::CandidacyBond::get(); const VotingBond: BalanceOf = T::VotingBond::get(); const DesiredMembers: u32 = T::DesiredMembers::get(); @@ -265,8 +321,8 @@ decl_module! { locked_balance, WithdrawReasons::except(WithdrawReason::TransactionPayment), ); - >::insert(&who, locked_balance); - >::insert(&who, votes); + + Voting::::insert(&who, (locked_balance, votes)); } /// Remove `origin` as a voter. This removes the lock and returns the bond. @@ -444,11 +500,13 @@ decl_module! { } /// What to do at the end of each block. Checks if an election needs to happen or not. - fn on_initialize(n: T::BlockNumber) { + fn on_initialize(n: T::BlockNumber) -> Weight { if let Err(e) = Self::end_block(n) { print("Guru meditation"); print(e); } + + SimpleDispatchInfo::default().weigh_data(()) } } } @@ -474,14 +532,6 @@ decl_event!( } ); -impl MigrateAccount for Module { - fn migrate_account(a: &T::AccountId) { - if StakeOf::::migrate_key_from_blake(a).is_some() { - VotesOf::::migrate_key_from_blake(a); - } - } -} - impl Module { /// Attempts to remove a member `who`. If a runner up exists, it is used as the replacement. /// Otherwise, `Ok(false)` is returned to signal the caller. @@ -531,7 +581,7 @@ impl Module { /// /// State: O(1). fn is_voter(who: &T::AccountId) -> bool { - >::contains_key(who) + Voting::::contains_key(who) } /// Check if `who` is currently an active member. @@ -594,8 +644,7 @@ impl Module { /// lock. Optionally, it would also return the reserved voting bond if indicated by `unreserve`. fn do_remove_voter(who: &T::AccountId, unreserve: bool) { // remove storage and lock. - >::remove(who); - >::remove(who); + Voting::::remove(who); T::Currency::remove_lock(MODULE_ID, who); if unreserve { @@ -605,7 +654,12 @@ impl Module { /// The locked stake of a voter. fn locked_stake_of(who: &T::AccountId) -> BalanceOf { - Self::stake_of(who) + Voting::::get(who).0 + } + + /// The locked stake of a voter. + fn votes_of(who: &T::AccountId) -> Vec { + Voting::::get(who).1 } /// Check there's nothing to do this block. @@ -636,7 +690,7 @@ impl Module { let num_to_elect = desired_runners_up + desired_seats; let mut candidates = Self::candidates(); - // candidates who explicitly called `submit_candidacy`. Only these folks are at the risk of + // candidates who explicitly called `submit_candidacy`. Only these folks are at risk of // losing their bond. let exposed_candidates = candidates.clone(); // current members are always a candidate for the next round as well. @@ -645,15 +699,14 @@ impl Module { // previous runners_up are also always candidates for the next round. candidates.append(&mut Self::runners_up_ids()); - let voters_and_votes = VotesOf::::iter() - .map(|(v, i)| (v, i)) - .collect::)>>(); - let maybe_phragmen_result = sp_phragmen::elect::<_, _, _, T::CurrencyToVote, Perbill>( + let voters_and_votes = Voting::::iter() + .map(|(voter, (stake, targets))| { (voter, stake, targets) }) + .collect::>(); + let maybe_phragmen_result = sp_phragmen::elect::<_, _, T::CurrencyToVote, Perbill>( num_to_elect, 0, candidates, - voters_and_votes, - Self::locked_stake_of, + voters_and_votes.clone(), ); if let Some(phragmen_result) = maybe_phragmen_result { @@ -675,12 +728,18 @@ impl Module { .filter_map(|(m, a)| if a.is_zero() { None } else { Some(m) } ) .collect::>(); - let support_map = sp_phragmen::build_support_map::<_, _, _, T::CurrencyToVote, Perbill>( - &new_set, - &phragmen_result.assignments, - Self::locked_stake_of, + let stake_of = |who: &T::AccountId| -> ExtendedBalance { + , u64>>::convert( + Self::locked_stake_of(who) + ) as ExtendedBalance + }; + let staked_assignments = sp_phragmen::assignment_ratio_to_staked( + phragmen_result.assignments, + stake_of, ); + let (support_map, _) = build_support_map::(&new_set, &staked_assignments); + let to_balance = |e: ExtendedBalance| >>::convert(e); let new_set_with_stake = new_set @@ -698,12 +757,24 @@ impl Module { // split new set into winners and runners up. let split_point = desired_seats.min(new_set_with_stake.len()); let mut new_members = (&new_set_with_stake[..split_point]).to_vec(); - let most_popular = new_members.first().map(|x| x.0.clone()); // save the runners up as-is. They are sorted based on desirability. // sort and save the members. new_members.sort_by(|i, j| i.0.cmp(&j.0)); + let mut prime_votes: Vec<_> = new_members.iter().map(|c| (&c.0, BalanceOf::::zero())).collect(); + for (_, stake, targets) in voters_and_votes.into_iter() { + for (votes, who) in targets.iter() + .enumerate() + .map(|(votes, who)| ((MAXIMUM_VOTE - votes) as u32, who)) + { + if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) { + prime_votes[i].1 += stake * votes.into(); + } + } + } + let prime = prime_votes.into_iter().max_by_key(|x| x.1).map(|x| x.0.clone()); + // new_members_ids is sorted by account id. let new_members_ids = new_members .iter() @@ -731,7 +802,7 @@ impl Module { &outgoing.clone(), &new_members_ids, ); - T::ChangeMembers::set_prime(most_popular); + T::ChangeMembers::set_prime(prime); // outgoing candidates lose their bond. let mut to_burn_bond = outgoing.to_vec(); @@ -784,6 +855,18 @@ impl Contains for Module { Self::is_member(who) } fn sorted_members() -> Vec { Self::members_ids() } + + // A special function to populate members in this pallet for passing Origin + // checks in runtime benchmarking. + #[cfg(feature = "runtime-benchmarks")] + fn add(who: &T::AccountId) { + Members::::mutate(|members| { + match members.binary_search_by(|(a, _b)| a.cmp(who)) { + Ok(_) => (), + Err(pos) => members.insert(pos, (who.clone(), BalanceOf::::default())), + } + }) + } } #[cfg(test)] @@ -797,7 +880,7 @@ mod tests { Perbill, testing::Header, BuildStorage, traits::{BlakeTwo256, IdentityLookup, Block as BlockT}, }; - use crate as elections; + use crate as elections_phragmen; use frame_system as system; parameter_types! { @@ -825,7 +908,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } @@ -933,6 +1016,7 @@ mod tests { type Currency = Balances; type CurrencyToVote = CurrencyToVoteHandler; type ChangeMembers = TestChangeMembers; + type InitializeMembers = (); type CandidacyBond = CandidacyBond; type VotingBond = VotingBond; type TermDuration = TermDuration; @@ -954,11 +1038,12 @@ mod tests { { System: system::{Module, Call, Event}, Balances: pallet_balances::{Module, Call, Event, Config}, - Elections: elections::{Module, Call, Event}, + Elections: elections_phragmen::{Module, Call, Event, Config}, } ); pub struct ExtBuilder { + genesis_members: Vec<(u64, u64)>, balance_factor: u64, voter_bond: u64, term_duration: u64, @@ -968,6 +1053,7 @@ mod tests { impl Default for ExtBuilder { fn default() -> Self { Self { + genesis_members: vec![], balance_factor: 1, voter_bond: 2, desired_runners_up: 0, @@ -989,11 +1075,16 @@ mod tests { self.term_duration = duration; self } + pub fn genesis_members(mut self, members: Vec<(u64, u64)>) -> Self { + self.genesis_members = members; + self + } pub fn build(self) -> sp_io::TestExternalities { VOTING_BOND.with(|v| *v.borrow_mut() = self.voter_bond); TERM_DURATION.with(|v| *v.borrow_mut() = self.term_duration); DESIRED_RUNNERS_UP.with(|v| *v.borrow_mut() = self.desired_runners_up); - GenesisConfig { + MEMBERS.with(|m| *m.borrow_mut() = self.genesis_members.iter().map(|(m, _)| m.clone()).collect::>()); + let mut ext: sp_io::TestExternalities = GenesisConfig { pallet_balances: Some(pallet_balances::GenesisConfig::{ balances: vec![ (1, 10 * self.balance_factor), @@ -1004,12 +1095,17 @@ mod tests { (6, 60 * self.balance_factor) ], }), - }.build_storage().unwrap().into() + elections_phragmen: Some(elections_phragmen::GenesisConfig:: { + members: self.genesis_members + }), + }.build_storage().unwrap().into(); + ext.execute_with(|| System::set_block_number(1)); + ext } } fn all_voters() -> Vec { - >::iter().map(|(v, _)| v).collect::>() + Voting::::iter().map(|(v, _)| v).collect::>() } fn balances(who: &u64) -> (u64, u64) { @@ -1025,7 +1121,6 @@ mod tests { #[test] fn params_should_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); assert_eq!(Elections::desired_members(), 2); assert_eq!(Elections::term_duration(), 5); assert_eq!(Elections::election_rounds(), 0); @@ -1042,6 +1137,37 @@ mod tests { }); } + #[test] + fn genesis_members_should_work() { + ExtBuilder::default().genesis_members(vec![(1, 10), (2, 20)]).build().execute_with(|| { + System::set_block_number(1); + assert_eq!(Elections::members(), vec![(1, 10), (2, 20)]); + + assert_eq!(Elections::voting(1), (10, vec![1])); + assert_eq!(Elections::voting(2), (20, vec![2])); + + // they will persist since they have self vote. + System::set_block_number(5); + assert_ok!(Elections::end_block(System::block_number())); + + assert_eq!(Elections::members_ids(), vec![1, 2]); + }) + } + + #[test] + #[should_panic = "Genesis member does not have enough stake"] + fn genesis_members_cannot_over_stake_0() { + // 10 cannot lock 20 as their stake and extra genesis will panic. + ExtBuilder::default().genesis_members(vec![(1, 20), (2, 20)]).build(); + } + + #[test] + #[should_panic] + fn genesis_members_cannot_over_stake_1() { + // 10 cannot reserve 20 as voting bond and extra genesis will panic. + ExtBuilder::default().voter_bond(20).genesis_members(vec![(1, 10), (2, 20)]).build(); + } + #[test] fn term_duration_zero_is_passive() { ExtBuilder::default() @@ -1049,7 +1175,6 @@ mod tests { .build() .execute_with(|| { - System::set_block_number(1); assert_eq!(Elections::term_duration(), 0); assert_eq!(Elections::desired_members(), 2); assert_eq!(Elections::election_rounds(), 0); @@ -1228,13 +1353,13 @@ mod tests { assert_eq!(balances(&2), (18, 2)); assert_eq!(has_lock(&2), 20); - assert_eq!(Elections::stake_of(2), 20); + assert_eq!(Elections::locked_stake_of(&2), 20); // can update; different stake; different lock and reserve. assert_ok!(Elections::vote(Origin::signed(2), vec![5, 4], 15)); assert_eq!(balances(&2), (18, 2)); assert_eq!(has_lock(&2), 15); - assert_eq!(Elections::stake_of(2), 15); + assert_eq!(Elections::locked_stake_of(&2), 15); }); } @@ -1290,6 +1415,31 @@ mod tests { }); } + #[test] + fn prime_votes_for_exiting_members_are_removed() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Elections::submit_candidacy(Origin::signed(3))); + assert_ok!(Elections::submit_candidacy(Origin::signed(4))); + assert_ok!(Elections::submit_candidacy(Origin::signed(5))); + + assert_ok!(Elections::vote(Origin::signed(1), vec![4, 3], 10)); + assert_ok!(Elections::vote(Origin::signed(2), vec![4], 20)); + assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30)); + assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40)); + assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50)); + + assert_ok!(Elections::renounce_candidacy(Origin::signed(4))); + + System::set_block_number(5); + assert_ok!(Elections::end_block(System::block_number())); + + assert_eq!(Elections::members_ids(), vec![3, 5]); + assert_eq!(Elections::candidates(), vec![]); + + assert_eq!(PRIME.with(|p| *p.borrow()), Some(5)); + }); + } + #[test] fn cannot_vote_for_more_than_candidates() { ExtBuilder::default().build().execute_with(|| { @@ -1324,7 +1474,7 @@ mod tests { assert_ok!(Elections::vote(Origin::signed(2), vec![4, 5], 30)); // you can lie but won't get away with it. - assert_eq!(Elections::stake_of(2), 20); + assert_eq!(Elections::locked_stake_of(&2), 20); assert_eq!(has_lock(&2), 20); }); } @@ -1338,16 +1488,16 @@ mod tests { assert_ok!(Elections::vote(Origin::signed(3), vec![5], 30)); assert_eq_uvec!(all_voters(), vec![2, 3]); - assert_eq!(Elections::stake_of(2), 20); - assert_eq!(Elections::stake_of(3), 30); - assert_eq!(Elections::votes_of(2), vec![5]); - assert_eq!(Elections::votes_of(3), vec![5]); + assert_eq!(Elections::locked_stake_of(&2), 20); + assert_eq!(Elections::locked_stake_of(&3), 30); + assert_eq!(Elections::votes_of(&2), vec![5]); + assert_eq!(Elections::votes_of(&3), vec![5]); assert_ok!(Elections::remove_voter(Origin::signed(2))); assert_eq_uvec!(all_voters(), vec![3]); - assert_eq!(Elections::votes_of(2), vec![]); - assert_eq!(Elections::stake_of(2), 0); + assert_eq!(Elections::votes_of(&2), vec![]); + assert_eq!(Elections::locked_stake_of(&2), 0); assert_eq!(balances(&2), (20, 0)); assert_eq!(Balances::locks(&2).len(), 0); @@ -1465,10 +1615,9 @@ mod tests { assert_eq!(balances(&5), (45, 5)); assert_ok!(Elections::report_defunct_voter(Origin::signed(5), 3)); - assert_eq!( - System::events()[7].event, - Event::elections(RawEvent::VoterReported(3, 5, true)) - ); + assert!(System::events().iter().any(|event| { + event.event == Event::elections_phragmen(RawEvent::VoterReported(3, 5, true)) + })); assert_eq!(balances(&3), (28, 0)); assert_eq!(balances(&5), (47, 5)); @@ -1494,10 +1643,9 @@ mod tests { assert_eq!(balances(&5), (45, 5)); assert_ok!(Elections::report_defunct_voter(Origin::signed(5), 4)); - assert_eq!( - System::events()[7].event, - Event::elections(RawEvent::VoterReported(4, 5, false)) - ); + assert!(System::events().iter().any(|event| { + event.event == Event::elections_phragmen(RawEvent::VoterReported(4, 5, false)) + })); assert_eq!(balances(&4), (35, 5)); assert_eq!(balances(&5), (45, 3)); @@ -1518,9 +1666,9 @@ mod tests { assert_eq_uvec!(all_voters(), vec![2, 3, 4]); - assert_eq!(Elections::votes_of(2), vec![5]); - assert_eq!(Elections::votes_of(3), vec![3]); - assert_eq!(Elections::votes_of(4), vec![4]); + assert_eq!(Elections::votes_of(&2), vec![5]); + assert_eq!(Elections::votes_of(&3), vec![3]); + assert_eq!(Elections::votes_of(&4), vec![4]); assert_eq!(Elections::candidates(), vec![3, 4, 5]); assert_eq!(>::decode_len().unwrap(), 3); @@ -1905,10 +2053,9 @@ mod tests { // 5 is an outgoing loser. will also get slashed. assert_eq!(balances(&5), (45, 2)); - assert_eq!( - System::events()[6].event, - Event::elections(RawEvent::NewTerm(vec![(4, 40), (5, 50)])), - ); + assert!(System::events().iter().any(|event| { + event.event == Event::elections_phragmen(RawEvent::NewTerm(vec![(4, 40), (5, 50)])) + })); }) } diff --git a/frame/elections/Cargo.toml b/frame/elections/Cargo.toml index e65f4e5d4627827380cbb06cc20732e0de40a5dc..6043ac4681e212afb462540552af02639c8af4bd 100644 --- a/frame/elections/Cargo.toml +++ b/frame/elections/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-elections" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,17 +10,17 @@ description = "FRAME pallet for elections" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] hex-literal = "0.2.1" -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } [features] default = ["std"] @@ -34,3 +34,6 @@ std = [ "sp-runtime/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/elections/src/lib.rs b/frame/elections/src/lib.rs index 6f89c4b9386b532a9ea000fb6b0dcc8b896b6d8b..a8ea0b8c2e4555d8990ba485e749f3c49b11024c 100644 --- a/frame/elections/src/lib.rs +++ b/frame/elections/src/lib.rs @@ -30,7 +30,7 @@ use sp_runtime::{ }; use frame_support::{ decl_storage, decl_event, ensure, decl_module, decl_error, - weights::SimpleDispatchInfo, + weights::{Weight, SimpleDispatchInfo, WeighData}, traits::{ Currency, ExistenceRequirement, Get, LockableCurrency, LockIdentifier, BalanceStatus, OnUnbalanced, ReservableCurrency, WithdrawReason, WithdrawReasons, ChangeMembers @@ -698,11 +698,12 @@ decl_module! { >::put(count); } - fn on_initialize(n: T::BlockNumber) { + fn on_initialize(n: T::BlockNumber) -> Weight { if let Err(e) = Self::end_block(n) { print("Guru meditation"); print(e); } + SimpleDispatchInfo::default().weigh_data(()) } } } diff --git a/frame/elections/src/mock.rs b/frame/elections/src/mock.rs index 2a5e5fefbd4a019e5a5360afb68ade7131975027..2898be26ca3001200d1806297b4695a655e212ea 100644 --- a/frame/elections/src/mock.rs +++ b/frame/elections/src/mock.rs @@ -55,7 +55,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } @@ -208,7 +208,7 @@ impl ExtBuilder { 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); - GenesisConfig { + let mut ext: sp_io::TestExternalities = GenesisConfig { pallet_balances: Some(pallet_balances::GenesisConfig::{ balances: vec![ (1, 10 * self.balance_factor), @@ -225,7 +225,9 @@ impl ExtBuilder { presentation_duration: 2, term_duration: 5, }), - }.build_storage().unwrap().into() + }.build_storage().unwrap().into(); + ext.execute_with(|| System::set_block_number(1)); + ext } } diff --git a/frame/elections/src/tests.rs b/frame/elections/src/tests.rs index e26f0312903fcb61c7b94fe2d93e7ac0da77e86b..64b01f12e0cfe8c3b9971bee6e3772f4a2fb2adf 100644 --- a/frame/elections/src/tests.rs +++ b/frame/elections/src/tests.rs @@ -26,7 +26,6 @@ use frame_support::{assert_ok, assert_err, assert_noop}; #[test] fn params_should_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); assert_eq!(Elections::next_vote_from(1), 4); assert_eq!(Elections::next_vote_from(4), 4); assert_eq!(Elections::next_vote_from(5), 8); @@ -408,8 +407,6 @@ fn voting_locking_stake_and_reserving_bond_works() { #[test] fn voting_without_any_candidate_count_should_not_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_eq!(Elections::candidates().len(), 0); assert_noop!( @@ -422,8 +419,6 @@ fn voting_without_any_candidate_count_should_not_work() { #[test] fn voting_setting_an_approval_vote_count_more_than_candidate_count_should_not_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); assert_eq!(Elections::candidates().len(), 1); @@ -437,8 +432,6 @@ fn voting_setting_an_approval_vote_count_more_than_candidate_count_should_not_wo #[test] fn voting_resubmitting_approvals_should_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); assert_ok!(Elections::set_approvals(Origin::signed(4), vec![true], 0, 0, 40)); @@ -456,8 +449,6 @@ fn voting_resubmitting_approvals_should_work() { #[test] fn voting_retracting_voter_should_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); assert_eq!(Elections::candidates().len(), 1); @@ -501,7 +492,6 @@ fn voting_retracting_voter_should_work() { #[test] fn voting_invalid_retraction_index_should_not_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); assert_ok!(Elections::submit_candidacy(Origin::signed(3), 0)); assert_ok!(Elections::set_approvals(Origin::signed(1), vec![true], 0, 0, 10)); @@ -514,7 +504,6 @@ fn voting_invalid_retraction_index_should_not_work() { #[test] fn voting_overflow_retraction_index_should_not_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); assert_ok!(Elections::submit_candidacy(Origin::signed(3), 0)); assert_ok!(Elections::set_approvals(Origin::signed(1), vec![true], 0, 0, 10)); @@ -525,7 +514,6 @@ fn voting_overflow_retraction_index_should_not_work() { #[test] fn voting_non_voter_retraction_should_not_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); assert_ok!(Elections::submit_candidacy(Origin::signed(3), 0)); assert_ok!(Elections::set_approvals(Origin::signed(1), vec![true], 0, 0, 10)); @@ -740,7 +728,6 @@ fn retracting_inactive_voter_by_nonvoter_should_not_work() { #[test] fn candidacy_simple_candidate_submission_should_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); assert_eq!(Elections::candidates(), Vec::::new()); assert_eq!(Elections::candidate_reg_info(1), None); assert_eq!(Elections::candidate_reg_info(2), None); @@ -768,7 +755,6 @@ fn candidacy_submission_using_free_slot_should_work() { let mut t = new_test_ext_with_candidate_holes(); t.execute_with(|| { - System::set_block_number(1); assert_eq!(Elections::candidates(), vec![0, 0, 1]); assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1)); @@ -784,7 +770,6 @@ fn candidacy_submission_using_alternative_free_slot_should_work() { let mut t = new_test_ext_with_candidate_holes(); t.execute_with(|| { - System::set_block_number(1); assert_eq!(Elections::candidates(), vec![0, 0, 1]); assert_ok!(Elections::submit_candidacy(Origin::signed(2), 0)); @@ -800,7 +785,6 @@ fn candidacy_submission_not_using_free_slot_should_not_work() { let mut t = new_test_ext_with_candidate_holes(); t.execute_with(|| { - System::set_block_number(1); assert_noop!( Elections::submit_candidacy(Origin::signed(4), 3), Error::::InvalidCandidateSlot @@ -811,7 +795,6 @@ fn candidacy_submission_not_using_free_slot_should_not_work() { #[test] fn candidacy_bad_candidate_slot_submission_should_not_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); assert_eq!(Elections::candidates(), Vec::::new()); assert_noop!( Elections::submit_candidacy(Origin::signed(1), 1), @@ -823,7 +806,6 @@ fn candidacy_bad_candidate_slot_submission_should_not_work() { #[test] fn candidacy_non_free_candidate_slot_submission_should_not_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); assert_eq!(Elections::candidates(), Vec::::new()); assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); assert_eq!(Elections::candidates(), vec![1]); @@ -837,7 +819,6 @@ fn candidacy_non_free_candidate_slot_submission_should_not_work() { #[test] fn candidacy_dupe_candidate_submission_should_not_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); assert_eq!(Elections::candidates(), Vec::::new()); assert_ok!(Elections::submit_candidacy(Origin::signed(1), 0)); assert_eq!(Elections::candidates(), vec![1]); @@ -851,7 +832,6 @@ fn candidacy_dupe_candidate_submission_should_not_work() { #[test] fn candidacy_poor_candidate_submission_should_not_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); assert_eq!(Elections::candidates(), Vec::::new()); assert_noop!( Elections::submit_candidacy(Origin::signed(7), 0), @@ -863,8 +843,6 @@ fn candidacy_poor_candidate_submission_should_not_work() { #[test] fn election_voting_should_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); assert_ok!(Elections::set_approvals(Origin::signed(1), vec![true], 0, 0, 10)); @@ -892,8 +870,6 @@ fn election_voting_should_work() { #[test] fn election_proxy_voting_should_work() { ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0)); >::insert(11, 1); diff --git a/frame/evm/Cargo.toml b/frame/evm/Cargo.toml index 76407d57e42846394039d3f9af9f5dfb70f2016d..95b3c24d8796e683c30c60d6a2cb3e3a5ffd4990 100644 --- a/frame/evm/Cargo.toml +++ b/frame/evm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-evm" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,18 +10,18 @@ description = "FRAME EVM contracts pallet" [dependencies] serde = { version = "1.0.101", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -pallet-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../timestamp" } -pallet-balances = { version = "2.0.0-alpha.2", default-features = false, path = "../balances" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -primitive-types = { version = "0.6.2", default-features = false, features = ["rlp"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +pallet-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../timestamp" } +pallet-balances = { version = "2.0.0-alpha.5", default-features = false, path = "../balances" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +primitive-types = { version = "0.7.0", default-features = false, features = ["rlp"] } rlp = { version = "0.4", default-features = false } -evm = { version = "0.15", default-features = false } +evm = { version = "0.16", default-features = false } sha3 = { version = "0.8", default-features = false } [features] @@ -42,3 +42,6 @@ std = [ "evm/std", "pallet-timestamp/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/evm/src/backend.rs b/frame/evm/src/backend.rs index d72c8b785e3468762054c9bd028fe3e3a1b730db..c610f24bb1db2d763979e978dd183c5719a731b9 100644 --- a/frame/evm/src/backend.rs +++ b/frame/evm/src/backend.rs @@ -7,7 +7,6 @@ use sp_core::{U256, H256, H160}; use sp_runtime::traits::UniqueSaturatedInto; use frame_support::storage::{StorageMap, StorageDoubleMap}; use sha3::{Keccak256, Digest}; -use evm::Config; use evm::backend::{Backend as BackendT, ApplyBackend, Apply}; use crate::{Trait, Accounts, AccountStorages, AccountCodes, Module, Event}; @@ -43,10 +42,6 @@ pub struct Vicinity { pub origin: H160, } -/// Gasometer config used for executor. Currently this is hard-coded to -/// Istanbul hard fork. -pub static GASOMETER_CONFIG: Config = Config::istanbul(); - /// Substrate backend for EVM. pub struct Backend<'vicinity, T> { vicinity: &'vicinity Vicinity, @@ -177,7 +172,7 @@ impl<'vicinity, T: Trait> ApplyBackend for Backend<'vicinity, T> { } for log in logs { - Module::::deposit_event(Event::Log(Log { + Module::::deposit_event(Event::::Log(Log { address: log.address, topics: log.topics, data: log.data, diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index 5a9cb852fd362ac23f5f90682173be333df4dd1b..a50c545a461c1937d9d58eb06a86ff372558ca66 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -35,7 +35,7 @@ use sp_runtime::{ DispatchResult, traits::{UniqueSaturatedInto, AccountIdConversion, SaturatedConversion}, }; use sha3::{Digest, Keccak256}; -use evm::{ExitReason, ExitSucceed, ExitError}; +use evm::{ExitReason, ExitSucceed, ExitError, Config}; use evm::executor::StackExecutor; use evm::backend::ApplyBackend; @@ -116,6 +116,8 @@ impl Precompiles for () { } } +static ISTANBUL_CONFIG: Config = Config::istanbul(); + /// EVM module trait pub trait Trait: frame_system::Trait + pallet_timestamp::Trait { /// Calculator for current gas price. @@ -125,9 +127,14 @@ pub trait Trait: frame_system::Trait + pallet_timestamp::Trait { /// Currency type for deposit and withdraw. type Currency: Currency; /// The overarching event type. - type Event: From + Into<::Event>; + type Event: From> + Into<::Event>; /// Precompiles associated with this EVM engine. type Precompiles: Precompiles; + + /// EVM config used in the module. + fn config() -> &'static Config { + &ISTANBUL_CONFIG + } } decl_storage! { @@ -140,11 +147,17 @@ decl_storage! { decl_event! { /// EVM events - pub enum Event { + pub enum Event where + ::AccountId, + { /// Ethereum events from contracts. Log(Log), /// A contract has been created at given address. Created(H160), + /// A deposit has been made at a given address. + BalanceDeposit(AccountId, H160, U256), + /// A withdrawal has been made from a given address. + BalanceWithdraw(AccountId, H160, U256), } } @@ -195,6 +208,7 @@ decl_module! { Accounts::mutate(&address, |account| { account.balance += bvalue; }); + Module::::deposit_event(Event::::BalanceDeposit(sender, address, bvalue)); } /// Withdraw balance from EVM into currency/balances module. @@ -218,6 +232,7 @@ decl_module! { Accounts::insert(&address, account); T::Currency::resolve_creating(&sender, imbalance); + Module::::deposit_event(Event::::BalanceWithdraw(sender, address, bvalue)); } /// Issue an EVM call operation. This is similar to a message call transaction in Ethereum. @@ -282,7 +297,7 @@ decl_module! { }, )?; - Module::::deposit_event(Event::Created(create_address)); + Module::::deposit_event(Event::::Created(create_address)); Ok(()) } @@ -320,7 +335,7 @@ decl_module! { }, )?; - Module::::deposit_event(Event::Created(create_address)); + Module::::deposit_event(Event::::Created(create_address)); Ok(()) } } @@ -381,7 +396,7 @@ impl Module { let mut executor = StackExecutor::new_with_precompile( &backend, gas_limit as usize, - &backend::GASOMETER_CONFIG, + T::config(), T::Precompiles::execute, ); diff --git a/frame/example-offchain-worker/Cargo.toml b/frame/example-offchain-worker/Cargo.toml index ff8d780c3cff326c3b1fb749270dc8ec09c7043a..6b3452d01894f30c46fcfefb8c81f7e0125ae9df 100644 --- a/frame/example-offchain-worker/Cargo.toml +++ b/frame/example-offchain-worker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-example-offchain-worker" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "Unlicense" @@ -9,15 +9,15 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME example pallet for offchain worker" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } serde = { version = "1.0.101", optional = true } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -serde_json = { version = "1.0.46", default-features = false, features = ["alloc"] } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +lite-json = { version = "0.1", default-features = false } [features] default = ["std"] @@ -26,8 +26,12 @@ std = [ "frame-support/std", "frame-system/std", "serde", + "lite-json/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/example-offchain-worker/src/lib.rs b/frame/example-offchain-worker/src/lib.rs index d4905d26b00d0866de2bd2544612b5bc458c6965..e64b3dfa7757c40c1d49bf4d1c99b6ecf881fbc7 100644 --- a/frame/example-offchain-worker/src/lib.rs +++ b/frame/example-offchain-worker/src/lib.rs @@ -47,13 +47,16 @@ use frame_support::{ weights::SimpleDispatchInfo, }; use frame_system::{self as system, ensure_signed, ensure_none, offchain}; -use serde_json as json; use sp_core::crypto::KeyTypeId; use sp_runtime::{ offchain::{http, Duration, storage::StorageValueRef}, traits::Zero, - transaction_validity::{InvalidTransaction, ValidTransaction, TransactionValidity}, + transaction_validity::{ + InvalidTransaction, ValidTransaction, TransactionValidity, TransactionSource, + }, }; +use sp_std::{vec, vec::Vec}; +use lite_json::json::JsonValue; #[cfg(test)] mod tests; @@ -276,7 +279,7 @@ impl Module { match last_send { // If we already have a value in storage and the block number is recent enough // we avoid sending another transaction at this time. - Some(Some(block)) if block + T::GracePeriod::get() < block_number => { + Some(Some(block)) if block_number < block + T::GracePeriod::get() => { Err(RECENTLY_SENT) }, // In every other case we attempt to acquire the lock and send a transaction. @@ -320,7 +323,7 @@ impl Module { } /// A helper function to fetch the price and send signed transaction. - fn fetch_price_and_send_signed() -> Result<(), String> { + fn fetch_price_and_send_signed() -> Result<(), &'static str> { use system::offchain::SubmitSignedTransaction; // Firstly we check if there are any accounts in the local keystore that are capable of // signing the transaction. @@ -334,7 +337,7 @@ impl Module { // Make an external HTTP request to fetch the current price. // Note this call will block until response is received. - let price = Self::fetch_price().map_err(|e| format!("{:?}", e))?; + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; // Received price is wrapped into a call to `submit_price` public function of this pallet. // This means that the transaction, when executed, will simply call that function passing @@ -357,20 +360,18 @@ impl Module { } /// A helper function to fetch the price and send unsigned transaction. - fn fetch_price_and_send_unsigned(block_number: T::BlockNumber) -> Result<(), String> { + fn fetch_price_and_send_unsigned(block_number: T::BlockNumber) -> Result<(), &'static str> { use system::offchain::SubmitUnsignedTransaction; // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. let next_unsigned_at = >::get(); if next_unsigned_at > block_number { - return Err( - format!("Too early to send unsigned transaction. Next at: {:?}", next_unsigned_at) - )? + return Err("Too early to send unsigned transaction") } // Make an external HTTP request to fetch the current price. // Note this call will block until response is received. - let price = Self::fetch_price().map_err(|e| format!("{:?}", e))?; + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; // Received price is wrapped into a call to `submit_price_unsigned` public function of this // pallet. This means that the transaction, when executed, will simply call that function @@ -428,21 +429,17 @@ impl Module { // Note that the return object allows you to read the body in chunks as well // with a way to control the deadline. let body = response.body().collect::>(); - // Next we parse the response using `serde_json`. Even though it's possible to use - // `serde_derive` and deserialize to a struct it's not recommended due to blob size - // overhead introduced by such code. Deserializing to `json::Value` is much more - // lightweight and should be preferred, especially if we only care about a small number - // of properties from the response. - let val: Result = json::from_slice(&body); - // Let's parse the price as float value. Note that you should avoid using floats in the - // runtime, it's fine to do that in the offchain worker, but we do convert it to an integer - // before submitting on-chain. - let price = val.ok().and_then(|v| v.get("USD").and_then(|v| v.as_f64())); - let price = match price { - Some(pricef) => Ok((pricef * 100.) as u32), + + // Create a str slice from the body. + let body_str = sp_std::str::from_utf8(&body).map_err(|_| { + debug::warn!("No UTF8 body"); + http::Error::Unknown + })?; + + let price = match Self::parse_price(body_str) { + Some(price) => Ok(price), None => { - let s = core::str::from_utf8(&body); - debug::warn!("Unable to extract price from the response: {:?}", s); + debug::warn!("Unable to extract price from the response: {:?}", body_str); Err(http::Error::Unknown) } }?; @@ -452,6 +449,28 @@ impl Module { Ok(price) } + /// Parse the price from the given JSON string using `lite-json`. + /// + /// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful. + fn parse_price(price_str: &str) -> Option { + let val = lite_json::parse_json(price_str); + let price = val.ok().and_then(|v| match v { + JsonValue::Object(obj) => { + let mut chars = "USD".chars(); + obj.into_iter() + .find(|(k, _)| k.iter().all(|k| Some(*k) == chars.next())) + .and_then(|v| match v.1 { + JsonValue::Number(number) => Some(number), + _ => None, + }) + }, + _ => None + })?; + + let exp = price.fraction_length.checked_sub(2).unwrap_or(0); + 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) { debug::info!("Adding to the average: {}", price); @@ -492,7 +511,10 @@ impl frame_support::unsigned::ValidateUnsigned for Module { /// By default unsigned transactions are disallowed, but implementing the validator /// here we make sure that some particular calls (the ones produced by offchain worker) /// are being whitelisted and marked as valid. - fn validate_unsigned(call: &Self::Call) -> TransactionValidity { + fn validate_unsigned( + _source: TransactionSource, + call: &Self::Call, + ) -> TransactionValidity { // Firstly let's check that we call the right function. if let Call::submit_price_unsigned(block_number, new_price) = call { // Now let's check if the transaction has any chance to succeed. diff --git a/frame/example-offchain-worker/src/tests.rs b/frame/example-offchain-worker/src/tests.rs index 5e086aa3a6ed10379e5be2b68fdb7b61b28c09cb..f64503b0a92a84a33fb3d4949e2dd1c575882a8d 100644 --- a/frame/example-offchain-worker/src/tests.rs +++ b/frame/example-offchain-worker/src/tests.rs @@ -66,7 +66,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } @@ -132,7 +132,7 @@ fn should_make_http_call_and_parse_result() { // when let price = Example::fetch_price().unwrap(); // then - assert_eq!(price, 15522); + assert_eq!(price, 15523); }); } @@ -164,7 +164,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::submit_price(15522)); + assert_eq!(tx.call, Call::submit_price(15523)); }); } @@ -186,7 +186,7 @@ fn should_submit_unsigned_transaction_on_chain() { assert!(pool_state.read().transactions.is_empty()); let tx = Extrinsic::decode(&mut &*tx).unwrap(); assert_eq!(tx.signature, None); - assert_eq!(tx.call, Call::submit_price_unsigned(1, 15522)); + assert_eq!(tx.call, Call::submit_price_unsigned(1, 15523)); }); } @@ -208,3 +208,19 @@ fn price_oracle_response(state: &mut testing::OffchainState) { ..Default::default() }); } + +#[test] +fn parse_price_works() { + let test_data = vec![ + ("{\"USD\":6536.92}", Some(653692)), + ("{\"USD\":65.92}", Some(6592)), + ("{\"USD\":6536.924565}", Some(653692)), + ("{\"USD\":6536}", Some(653600)), + ("{\"USD2\":6536}", None), + ("{\"USD\":\"6432\"}", None), + ]; + + for (json, expected) in test_data { + assert_eq!(expected, Example::parse_price(json)); + } +} diff --git a/frame/example/Cargo.toml b/frame/example/Cargo.toml index 515d1fd8d7c7a10b805b43cba89793f7f1fb67af..014bcc91d4bcb06051f97fd36038bbe9f72ab460 100644 --- a/frame/example/Cargo.toml +++ b/frame/example/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-example" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "Unlicense" @@ -10,17 +10,17 @@ description = "FRAME example pallet" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -frame-benchmarking = { version = "2.0.0-alpha.2", default-features = false, path = "../benchmarking" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -pallet-balances = { version = "2.0.0-alpha.2", default-features = false, path = "../balances" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +pallet-balances = { version = "2.0.0-alpha.5", default-features = false, path = "../balances" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core", default-features = false } [features] default = ["std"] @@ -35,3 +35,6 @@ std = [ "sp-io/std", "sp-std/std" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/example/src/lib.rs b/frame/example/src/lib.rs index 62e46e9cb2aa5fdb990e374a0637a507c743d85d..70b3472ea0ab9cf8ffa6231f7dd8d97deb5d6997 100644 --- a/frame/example/src/lib.rs +++ b/frame/example/src/lib.rs @@ -256,7 +256,10 @@ use sp_std::marker::PhantomData; use frame_support::{ dispatch::DispatchResult, decl_module, decl_storage, decl_event, - weights::{SimpleDispatchInfo, DispatchInfo, DispatchClass, ClassifyDispatch, WeighData, Weight, PaysFee}, + weights::{ + SimpleDispatchInfo, DispatchInfo, DispatchClass, ClassifyDispatch, WeighData, Weight, + PaysFee, + }, }; use sp_std::prelude::*; use frame_benchmarking::{benchmarks, account}; @@ -516,14 +519,14 @@ decl_module! { // This function could also very well have a weight annotation, similar to any other. The // only difference being that if it is not annotated, the default is // `SimpleDispatchInfo::zero()`, which resolves into no weight. - #[weight = SimpleDispatchInfo::FixedNormal(1000)] - fn on_initialize(_n: T::BlockNumber) { + 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. + + SimpleDispatchInfo::default().weigh_data(()) } // The signature could also look like: `fn on_finalize()` - #[weight = SimpleDispatchInfo::FixedNormal(2000)] fn on_finalize(_n: T::BlockNumber) { // Anything that needs to be done at the end of the block. // We just kill our dummy storage item. @@ -688,14 +691,17 @@ benchmarks!{ mod tests { use super::*; - use frame_support::{assert_ok, impl_outer_origin, parameter_types, weights::GetDispatchInfo}; + use frame_support::{ + assert_ok, impl_outer_origin, parameter_types, weights::GetDispatchInfo, + traits::{OnInitialize, OnFinalize} + }; use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. use sp_runtime::{ Perbill, testing::Header, - traits::{BlakeTwo256, OnInitialize, OnFinalize, IdentityLookup}, + traits::{BlakeTwo256, IdentityLookup}, }; impl_outer_origin! { @@ -731,7 +737,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } parameter_types! { diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index 44751b0f3fc538445422bce5ff8be025630c68bc..3c494199cb2374e07a45b1b888d3af52545681b8 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-executive" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,21 +9,21 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME executives engine" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } serde = { version = "1.0.101", optional = true } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } [dev-dependencies] hex-literal = "0.2.1" -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-io ={ path = "../../primitives/io", version = "2.0.0-alpha.2"} -pallet-indices = { version = "2.0.0-alpha.2", path = "../indices" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } -pallet-transaction-payment = { version = "2.0.0-alpha.2", path = "../transaction-payment" } -sp-version = { version = "2.0.0-alpha.2", path = "../../primitives/version" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-io ={ path = "../../primitives/io", version = "2.0.0-alpha.5"} +pallet-indices = { version = "2.0.0-alpha.5", path = "../indices" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } +pallet-transaction-payment = { version = "2.0.0-alpha.5", path = "../transaction-payment" } +sp-version = { version = "2.0.0-alpha.5", path = "../../primitives/version" } [features] default = ["std"] @@ -35,3 +35,6 @@ std = [ "sp-runtime/std", "sp-std/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 8fbcb3bc60f701178e5acdfd2b1b6eb8561dbbb7..d30b66e0837df9af76f7d5ae4da3231b95690c9f 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -59,12 +59,14 @@ //! # pub type Balances = u64; //! # pub type AllModules = u64; //! # pub enum Runtime {}; -//! # use sp_runtime::transaction_validity::{TransactionValidity, UnknownTransaction}; +//! # use sp_runtime::transaction_validity::{ +//! TransactionValidity, UnknownTransaction, TransactionSource, +//! # }; //! # use sp_runtime::traits::ValidateUnsigned; //! # impl ValidateUnsigned for Runtime { //! # type Call = (); //! # -//! # fn validate_unsigned(_call: &Self::Call) -> TransactionValidity { +//! # fn validate_unsigned(_source: TransactionSource, _call: &Self::Call) -> TransactionValidity { //! # UnknownTransaction::NoUnsignedValidator.into() //! # } //! # } @@ -75,16 +77,18 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_std::{prelude::*, marker::PhantomData}; -use frame_support::{storage::StorageValue, weights::{GetDispatchInfo, WeighBlock, DispatchInfo}}; +use frame_support::{ + storage::StorageValue, weights::{GetDispatchInfo, DispatchInfo}, + traits::{OnInitialize, OnFinalize, OnRuntimeUpgrade, OffchainWorker}, +}; use sp_runtime::{ generic::Digest, ApplyExtrinsicResult, traits::{ - self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalize, OnInitialize, - NumberFor, Block as BlockT, OffchainWorker, Dispatchable, Saturating, OnRuntimeUpgrade, + self, Header, Zero, One, Checkable, Applyable, CheckEqual, ValidateUnsigned, NumberFor, + Block as BlockT, Dispatchable, Saturating, }, - transaction_validity::TransactionValidity, + transaction_validity::{TransactionValidity, TransactionSource}, }; -use sp_runtime::traits::ValidateUnsigned; use codec::{Codec, Encode}; use frame_system::{extrinsics_root, DigestOf}; @@ -111,8 +115,7 @@ impl< OnRuntimeUpgrade + OnInitialize + OnFinalize + - OffchainWorker + - WeighBlock, + OffchainWorker, > ExecuteBlock for Executive where Block::Extrinsic: Checkable + Codec, @@ -137,8 +140,7 @@ impl< OnRuntimeUpgrade + OnInitialize + OnFinalize + - OffchainWorker + - WeighBlock, + OffchainWorker, > Executive where Block::Extrinsic: Checkable + Codec, @@ -179,10 +181,8 @@ where if Self::runtime_upgraded() { // System is not part of `AllModules`, so we need to call this manually. as OnRuntimeUpgrade>::on_runtime_upgrade(); - ::on_runtime_upgrade(); - >::register_extra_weight_unchecked( - >::on_runtime_upgrade() - ); + let weight = ::on_runtime_upgrade(); + >::register_extra_weight_unchecked(weight); } >::initialize( block_number, @@ -192,13 +192,10 @@ where frame_system::InitKind::Full, ); as OnInitialize>::on_initialize(*block_number); - >::on_initialize(*block_number); - >::register_extra_weight_unchecked( - >::on_initialize(*block_number) - ); - >::register_extra_weight_unchecked( - >::on_finalize(*block_number) - ); + let weight = >::on_initialize(*block_number); + >::register_extra_weight_unchecked(weight); + + frame_system::Module::::note_finished_initialize(); } /// Returns if the runtime was upgraded since the last time this function was called. @@ -343,12 +340,15 @@ where /// side-effects; it merely checks whether the transaction would panic if it were included or not. /// /// Changes made to storage should be discarded. - pub fn validate_transaction(uxt: Block::Extrinsic) -> TransactionValidity { + pub fn validate_transaction( + source: TransactionSource, + uxt: Block::Extrinsic, + ) -> TransactionValidity { let encoded_len = uxt.using_encoded(|d| d.len()); let xt = uxt.check(&Default::default())?; let dispatch_info = xt.get_dispatch_info(); - xt.validate::(dispatch_info, encoded_len) + xt.validate::(source, dispatch_info, encoded_len) } /// Start an offchain worker and generate extrinsics. @@ -398,7 +398,7 @@ mod tests { use hex_literal::hex; mod custom { - use frame_support::weights::SimpleDispatchInfo; + use frame_support::weights::{SimpleDispatchInfo, Weight}; pub trait Trait: frame_system::Trait {} @@ -420,11 +420,11 @@ mod tests { // module hooks. // one with block number arg and one without - #[weight = SimpleDispatchInfo::FixedNormal(25)] - fn on_initialize(n: T::BlockNumber) { + fn on_initialize(n: T::BlockNumber) -> Weight { println!("on_initialize({})", n); + 175 } - #[weight = SimpleDispatchInfo::FixedNormal(150)] + fn on_finalize() { println!("on_finalize(?)"); } @@ -481,7 +481,7 @@ mod tests { type Version = RuntimeVersion; type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } parameter_types! { @@ -516,7 +516,10 @@ mod tests { Ok(()) } - fn validate_unsigned(call: &Self::Call) -> TransactionValidity { + fn validate_unsigned( + _source: TransactionSource, + call: &Self::Call, + ) -> TransactionValidity { match call { Call::Balances(BalancesCall::set_balance(_, _, _)) => Ok(Default::default()), _ => UnknownTransaction::NoUnsignedValidator.into(), @@ -730,7 +733,10 @@ mod tests { let mut t = new_test_ext(1); t.execute_with(|| { - assert_eq!(Executive::validate_transaction(xt.clone()), Ok(Default::default())); + assert_eq!( + Executive::validate_transaction(TransactionSource::InBlock, xt.clone()), + Ok(Default::default()), + ); assert_eq!(Executive::apply_extrinsic(xt), Ok(Err(DispatchError::BadOrigin))); }); } diff --git a/frame/finality-tracker/Cargo.toml b/frame/finality-tracker/Cargo.toml index 1313080dfe0044ce3d0ee35ad7fcf895c071b4ab..e261fae05f5afd4bda7e69a0fc26e3d052890488 100644 --- a/frame/finality-tracker/Cargo.toml +++ b/frame/finality-tracker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-finality-tracker" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,18 +12,18 @@ documentation = "https://docs.rs/pallet-finality-tracker" [dependencies] serde = { version = "1.0.101", default-features = false, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/inherents" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-finality-tracker = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/finality-tracker" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-finality-tracker = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/finality-tracker" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } impl-trait-for-tuples = "0.1.3" [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } [features] default = ["std"] @@ -37,3 +37,6 @@ std = [ "sp-finality-tracker/std", "sp-inherents/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/finality-tracker/src/lib.rs b/frame/finality-tracker/src/lib.rs index 953861dedec0a0ae79b082bcf495a3d054773f6b..4a6e2392f2b31e5269bf083acd9176ff5deeebc8 100644 --- a/frame/finality-tracker/src/lib.rs +++ b/frame/finality-tracker/src/lib.rs @@ -76,6 +76,7 @@ decl_module! { /// Hint that the author of this block thinks the best finalized /// block is the given number. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn final_hint(origin, #[compact] hint: T::BlockNumber) { ensure_none(origin)?; ensure!(!::Update::exists(), Error::::AlreadyUpdated); @@ -207,9 +208,11 @@ mod tests { use sp_core::H256; use sp_runtime::{ testing::Header, Perbill, - traits::{BlakeTwo256, IdentityLookup, OnFinalize, Header as HeaderT}, + traits::{BlakeTwo256, IdentityLookup, Header as HeaderT}, + }; + use frame_support::{ + assert_ok, impl_outer_origin, parameter_types, weights::Weight, traits::OnFinalize }; - use frame_support::{assert_ok, impl_outer_origin, parameter_types, weights::Weight}; use frame_system as system; use std::cell::RefCell; @@ -262,7 +265,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } parameter_types! { diff --git a/frame/generic-asset/Cargo.toml b/frame/generic-asset/Cargo.toml index a8df92e3c61706c356117ff8c8403fe7b37ac8e0..b531a0ed9af24be72d5146a0d06ada267521a8e8 100644 --- a/frame/generic-asset/Cargo.toml +++ b/frame/generic-asset/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-generic-asset" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Centrality Developers "] edition = "2018" license = "GPL-3.0" @@ -10,15 +10,15 @@ description = "FRAME pallet for generic asset management" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -sp-io ={ version = "2.0.0-alpha.2", path = "../../primitives/io" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +sp-io ={ version = "2.0.0-alpha.5", path = "../../primitives/io" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } [features] default = ["std"] @@ -30,3 +30,6 @@ std =[ "frame-support/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/generic-asset/src/lib.rs b/frame/generic-asset/src/lib.rs index 29dbe3e7813a203a343daa10695e093dc0ce3821..b16666cb6b7e1c5dd96616a38035d141a70f371d 100644 --- a/frame/generic-asset/src/lib.rs +++ b/frame/generic-asset/src/lib.rs @@ -360,12 +360,14 @@ decl_module! { fn deposit_event() = default; /// Create a new kind of asset. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn create(origin, options: AssetOptions) -> DispatchResult { let origin = ensure_signed(origin)?; Self::create_asset(None, Some(origin), options) } /// Transfer some liquid free balance to another account. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn transfer(origin, #[compact] asset_id: T::AssetId, to: T::AccountId, #[compact] amount: T::Balance) { let origin = ensure_signed(origin)?; ensure!(!amount.is_zero(), Error::::ZeroAmount); @@ -375,6 +377,7 @@ decl_module! { /// Updates permission for a given `asset_id` and an account. /// /// The `origin` must have `update` permission. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn update_permission( origin, #[compact] asset_id: T::AssetId, @@ -397,6 +400,7 @@ decl_module! { /// Mints an asset, increases its total issuance. /// The origin must have `mint` permissions. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn mint(origin, #[compact] asset_id: T::AssetId, to: T::AccountId, amount: T::Balance) -> DispatchResult { let who = ensure_signed(origin)?; Self::mint_free(&asset_id, &who, &to, &amount)?; @@ -406,6 +410,7 @@ decl_module! { /// Burns an asset, decreases its total issuance. /// The `origin` must have `burn` permissions. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn burn(origin, #[compact] asset_id: T::AssetId, to: T::AccountId, amount: T::Balance) -> DispatchResult { let who = ensure_signed(origin)?; Self::burn_free(&asset_id, &who, &to, &amount)?; @@ -415,6 +420,7 @@ decl_module! { /// Can be used to create reserved tokens. /// Requires Root call. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn create_reserved( origin, asset_id: T::AssetId, @@ -1123,7 +1129,7 @@ impl frame_system::Trait for ElevatedTrait { type Version = T::Version; type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } impl Trait for ElevatedTrait { diff --git a/frame/generic-asset/src/mock.rs b/frame/generic-asset/src/mock.rs index 8daca2da38e39aa3b9d4ed934e910ce4b040e085..c805b793bc7b4e358e8c8f2b4b5045a8c14dd360 100644 --- a/frame/generic-asset/src/mock.rs +++ b/frame/generic-asset/src/mock.rs @@ -63,7 +63,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } @@ -127,16 +127,17 @@ impl ExtBuilder { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); GenesisConfig:: { - assets: vec![self.asset_id], - endowed_accounts: self.accounts, - initial_balance: self.initial_balance, - next_asset_id: self.next_asset_id, - staking_asset_id: 16000, - spending_asset_id: 16001, - } - .assimilate_storage(&mut t).unwrap(); - - t.into() + assets: vec![self.asset_id], + endowed_accounts: self.accounts, + initial_balance: self.initial_balance, + next_asset_id: self.next_asset_id, + staking_asset_id: 16000, + spending_asset_id: 16001, + }.assimilate_storage(&mut t).unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext } } diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index f5cce65fe906a55ca71bab5287b0f3ec69670d95..206b563bd9555c9f163d82473788a8baf3ecc275 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-grandpa" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,19 +10,19 @@ description = "FRAME pallet for GRANDPA finality gadget" [dependencies] serde = { version = "1.0.101", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-finality-grandpa = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/finality-grandpa" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/staking" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -pallet-session = { version = "2.0.0-alpha.2", default-features = false, path = "../session" } -pallet-finality-tracker = { version = "2.0.0-alpha.2", default-features = false, path = "../finality-tracker" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-finality-grandpa = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/finality-grandpa" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/staking" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +pallet-session = { version = "2.0.0-alpha.5", default-features = false, path = "../session" } +pallet-finality-tracker = { version = "2.0.0-alpha.5", default-features = false, path = "../finality-tracker" } [dev-dependencies] -sp-io ={ version = "2.0.0-alpha.2", path = "../../primitives/io" } +sp-io ={ version = "2.0.0-alpha.5", path = "../../primitives/io" } [features] default = ["std"] @@ -39,4 +39,6 @@ std = [ "pallet-session/std", "pallet-finality-tracker/std", ] -migrate-authorities = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index ab8c384dfb665f19754dff150921d41999c08f3d..030699b52587262eef974a923fd4074a7fb043e3 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -34,7 +34,7 @@ use sp_std::prelude::*; use codec::{self as codec, Encode, Decode}; use frame_support::{decl_event, decl_storage, decl_module, decl_error, storage}; use sp_runtime::{ - DispatchResult, generic::{DigestItem, OpaqueDigestItemId}, traits::Zero, Perbill, PerThing, + DispatchResult, generic::{DigestItem, OpaqueDigestItemId}, traits::Zero, Perbill, }; use sp_staking::{ SessionIndex, @@ -151,13 +151,6 @@ decl_error! { decl_storage! { trait Store for Module as GrandpaFinality { - /// DEPRECATED - /// - /// This used to store the current authority set, which has been migrated to the well-known - /// GRANDPA_AUTHORITIES_KEY unhashed key. - #[cfg(feature = "migrate-authorities")] - pub(crate) Authorities get(fn authorities): AuthorityList; - /// State of the current authority set. State get(fn state): StoredState = StoredState::Live; @@ -184,15 +177,6 @@ decl_storage! { } } -mod migration { - use super::*; - pub fn migrate() { - for i in 0..=CurrentSetId::get() { - SetIdSession::migrate_key_from_blake(i); - } - } -} - decl_module! { pub struct Module for enum Call where origin: T::Origin { type Error = Error; @@ -200,20 +184,12 @@ decl_module! { fn deposit_event() = default; /// Report some misbehavior. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn report_misbehavior(origin, _report: Vec) { ensure_signed(origin)?; // FIXME: https://github.com/paritytech/substrate/issues/1112 } - fn on_runtime_upgrade() { - migration::migrate::(); - } - - fn on_initialize() { - #[cfg(feature = "migrate-authorities")] - Self::migrate_authorities(); - } - fn on_finalize(block_number: T::BlockNumber) { // check for scheduled pending authority set changes if let Some(pending_change) = >::get() { @@ -384,13 +360,6 @@ impl Module { Self::set_grandpa_authorities(authorities); } } - - #[cfg(feature = "migrate-authorities")] - fn migrate_authorities() { - if Authorities::exists() { - Self::set_grandpa_authorities(&Authorities::take()); - } - } } impl Module { diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 24d7feab4922b504c52a509f52c65b6ce65b902a..8b94becd5aa6a123f846325b20e6e9632e8d67a6 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -66,7 +66,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/grandpa/src/tests.rs b/frame/grandpa/src/tests.rs index ff3841b8d4593dcb111492f830402c9a15eb5276..b583c31968d896e36e11811ff0193bea7a629ff9 100644 --- a/frame/grandpa/src/tests.rs +++ b/frame/grandpa/src/tests.rs @@ -18,7 +18,8 @@ #![cfg(test)] -use sp_runtime::{testing::{H256, Digest}, traits::{Header, OnFinalize}}; +use sp_runtime::{testing::{H256, Digest}, traits::Header}; +use frame_support::traits::OnFinalize; use crate::mock::*; use frame_system::{EventRecord, Phase}; use codec::{Decode, Encode}; @@ -318,21 +319,3 @@ fn time_slot_have_sane_ord() { ]; assert!(FIXTURE.windows(2).all(|f| f[0] < f[1])); } - -#[test] -#[cfg(feature = "migrate-authorities")] -fn authorities_migration() { - use sp_runtime::traits::OnInitialize; - - with_externalities(&mut new_test_ext(vec![]), || { - let authorities = to_authorities(vec![(1, 1), (2, 1), (3, 1)]); - - Authorities::put(authorities.clone()); - assert!(Grandpa::grandpa_authorities().is_empty()); - - Grandpa::on_initialize(1); - - assert!(!Authorities::exists()); - assert_eq!(Grandpa::grandpa_authorities(), authorities); - }); -} diff --git a/frame/identity/Cargo.toml b/frame/identity/Cargo.toml index 3dbffedffcdc3ea332807f8542989e04121c92f4..22b385d06d76f36677a50b2afcbe51ad564e970f 100644 --- a/frame/identity/Cargo.toml +++ b/frame/identity/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-identity" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,18 +10,18 @@ description = "FRAME identity management pallet" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } enumflags2 = { version = "0.6.2" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "2.0.0-alpha.2", default-features = false, path = "../benchmarking", optional = true } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } [features] default = ["std"] @@ -36,3 +36,6 @@ std = [ "frame-system/std", ] runtime-benchmarks = ["frame-benchmarking"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/identity/src/benchmarking.rs b/frame/identity/src/benchmarking.rs index 87d577c4d8e88b420760924812f18a9238c5d8f0..b5236e62191ae08bb8a301ce1f9dd85649edfbe3 100644 --- a/frame/identity/src/benchmarking.rs +++ b/frame/identity/src/benchmarking.rs @@ -16,6 +16,8 @@ //! Identity pallet benchmarking. +#![cfg(feature = "runtime-benchmarks")] + use super::*; use frame_system::RawOrigin; @@ -53,7 +55,7 @@ fn add_registrars(r: u32) -> Result<(), &'static str> { // Adds `s` sub-accounts to the identity of `who`. Each wil have 32 bytes of raw data added to it. // This additionally returns the vector of sub-accounts to it can be modified if needed. -fn add_sub_accounts(who: T::AccountId, s: u32) -> Result, &'static str> { +fn add_sub_accounts(who: &T::AccountId, s: u32) -> Result, &'static str> { let mut subs = Vec::new(); let who_origin = RawOrigin::Signed(who.clone()); let data = Data::Raw(vec![0; 32]); @@ -100,7 +102,7 @@ benchmarks! { let s in 1 .. T::MaxSubAccounts::get() => { // Give them s many sub accounts let caller = account::("caller", 0); - let _ = add_sub_accounts::(caller, s)?; + let _ = add_sub_accounts::(&caller, s)?; }; let x in 1 .. T::MaxAdditionalFields::get() => { // Create their main identity with x additional fields @@ -149,11 +151,19 @@ benchmarks! { ) set_subs { - let s in ...; - let caller = account::("caller", 0); - let caller_origin: ::Origin = RawOrigin::Signed(caller.clone()).into(); - let subs = Module::::subs(&caller); + + // Give them s many sub accounts. + let s in 1 .. T::MaxSubAccounts::get() - 1 => { + let _ = add_sub_accounts::(&caller, s)?; + }; + + let mut subs = Module::::subs(&caller); + + // Create an s + 1 sub account. + let data = Data::Raw(vec![0; 32]); + subs.push((account::("sub", s + 1), data)); + }: _(RawOrigin::Signed(caller), subs) clear_identity { diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index d3098dcde210297dcf18f70aa7aae7b032f9b74d..e5b1fe68bb990fc4d000009f48073df1146f60e1 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -77,10 +77,8 @@ use frame_support::{ weights::SimpleDispatchInfo, }; use frame_system::{self as system, ensure_signed, ensure_root}; -use frame_support::traits::MigrateAccount; -#[cfg(feature = "runtime-benchmarks")] -pub mod benchmarking; +mod benchmarking; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; @@ -887,18 +885,6 @@ impl Module { } } -impl MigrateAccount for Module { - fn migrate_account(a: &T::AccountId) { - if IdentityOf::::migrate_key_from_blake(a).is_some() { - if let Some((_, subs)) = SubsOf::::migrate_key_from_blake(a) { - for sub in subs.into_iter() { - SuperOf::::migrate_key_from_blake(sub); - } - } - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -949,7 +935,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } parameter_types! { diff --git a/frame/im-online/Cargo.toml b/frame/im-online/Cargo.toml index ab8c066945480dee7feaf11fb14873e71695bfa0..dabcc45ef166af7fdf5c47e6a86bbc41732017a0 100644 --- a/frame/im-online/Cargo.toml +++ b/frame/im-online/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-im-online" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,18 +9,20 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME's I'm online pallet" [dependencies] -sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/application-crypto" } -pallet-authorship = { version = "2.0.0-alpha.2", default-features = false, path = "../authorship" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } +sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/application-crypto" } +pallet-authorship = { version = "2.0.0-alpha.5", default-features = false, path = "../authorship" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } serde = { version = "1.0.101", optional = true } -pallet-session = { version = "2.0.0-alpha.2", default-features = false, path = "../session" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/staking" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +pallet-session = { version = "2.0.0-alpha.5", default-features = false, path = "../session" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/staking" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } + +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } [features] default = ["std", "pallet-session/historical"] @@ -38,3 +40,7 @@ std = [ "frame-support/std", "frame-system/std", ] +runtime-benchmarks = ["frame-benchmarking"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/im-online/src/benchmarking.rs b/frame/im-online/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..973bd0c36147c6c4c7e38eba55ef2fc490a0d85b --- /dev/null +++ b/frame/im-online/src/benchmarking.rs @@ -0,0 +1,124 @@ +// Copyright 2020 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 . + +//! I'm Online pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_system::RawOrigin; +use frame_benchmarking::benchmarks; +use sp_core::offchain::{OpaquePeerId, OpaqueMultiaddr}; +use sp_runtime::traits::{ValidateUnsigned, Zero}; +use sp_runtime::transaction_validity::TransactionSource; + +use crate::Module as ImOnline; + +const MAX_KEYS: u32 = 1000; +const MAX_EXTERNAL_ADDRESSES: u32 = 100; + +pub fn create_heartbeat(k: u32, e: u32) -> + Result<(crate::Heartbeat, ::Signature), &'static str> +{ + let mut keys = Vec::new(); + for _ in 0..k { + keys.push(T::AuthorityId::generate_pair(None)); + } + Keys::::put(keys.clone()); + + let network_state = OpaqueNetworkState { + peer_id: OpaquePeerId::default(), + external_addresses: vec![OpaqueMultiaddr::new(vec![0; 32]); e as usize], + }; + let input_heartbeat = Heartbeat { + block_number: T::BlockNumber::zero(), + network_state, + session_index: 0, + authority_index: k-1, + }; + + let encoded_heartbeat = input_heartbeat.encode(); + let authority_id = keys.get((k-1) as usize).ok_or("out of range")?; + let signature = authority_id.sign(&encoded_heartbeat).ok_or("couldn't make signature")?; + + Ok((input_heartbeat, signature)) +} + +benchmarks! { + _{ } + + heartbeat { + let k in 1 .. MAX_KEYS; + let e in 1 .. MAX_EXTERNAL_ADDRESSES; + let (input_heartbeat, signature) = create_heartbeat::(k, e)?; + }: _(RawOrigin::None, input_heartbeat, signature) + + validate_unsigned { + let k in 1 .. MAX_KEYS; + let e in 1 .. MAX_EXTERNAL_ADDRESSES; + let (input_heartbeat, signature) = create_heartbeat::(k, e)?; + let call = Call::heartbeat(input_heartbeat, signature); + }: { + ImOnline::::validate_unsigned(TransactionSource::InBlock, &call)?; + } +} + +#[cfg(test)] +mod tests { + use crate::*; + use super::SelectedBenchmark; + use crate::mock::*; + use frame_support::assert_ok; + + #[test] + fn test_heartbeat_benchmark() { + new_test_ext().execute_with(|| { + let k = 10; + + assert_eq!(ReceivedHeartbeats::iter_prefix(0).count(), 0); + + let selected_benchmark = SelectedBenchmark::heartbeat; + let c = vec![(frame_benchmarking::BenchmarkParameter::k, k)]; + let closure_to_benchmark = + >::instance( + &selected_benchmark, + &c + ).unwrap(); + + assert_ok!(closure_to_benchmark()); + + assert_eq!(ReceivedHeartbeats::iter_prefix(0).count(), 1); + }); + } + + #[test] + fn test_validate_unsigned_benchmark() { + new_test_ext().execute_with(|| { + let k = 10; + + let selected_benchmark = SelectedBenchmark::validate_unsigned; + let c = vec![(frame_benchmarking::BenchmarkParameter::k, k)]; + let closure_to_benchmark = + >::instance( + &selected_benchmark, + &c + ).unwrap(); + + assert_ok!(closure_to_benchmark()); + }); + } +} diff --git a/frame/im-online/src/lib.rs b/frame/im-online/src/lib.rs index 4d40decdfc1c764133b2132e02b8d374016691eb..cbce3095b69b54757ae9f6a0ccd11c454901a2bd 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -50,6 +50,7 @@ //! //! decl_module! { //! pub struct Module for enum Call where origin: T::Origin { +//! #[weight = frame_support::weights::SimpleDispatchInfo::default()] //! pub fn is_online(origin, authority_index: u32) -> dispatch::DispatchResult { //! let _sender = ensure_signed(origin)?; //! let _is_online = >::is_online(authority_index); @@ -69,6 +70,7 @@ mod mock; mod tests; +mod benchmarking; use sp_application_crypto::RuntimeAppPublic; use codec::{Encode, Decode}; @@ -79,9 +81,9 @@ use pallet_session::historical::IdentificationTuple; use sp_runtime::{ offchain::storage::StorageValueRef, RuntimeDebug, - traits::{Convert, Member, Saturating, AtLeast32Bit}, Perbill, PerThing, + traits::{Convert, Member, Saturating, AtLeast32Bit}, Perbill, transaction_validity::{ - TransactionValidity, ValidTransaction, InvalidTransaction, + TransactionValidity, ValidTransaction, InvalidTransaction, TransactionSource, TransactionPriority, }, }; @@ -95,7 +97,6 @@ use frame_support::{ }; use frame_system::{self as system, ensure_none}; use frame_system::offchain::SubmitUnsignedTransaction; -use frame_support::traits::MigrateAccount; pub mod sr25519 { mod app_sr25519 { @@ -303,38 +304,13 @@ decl_error! { } } -mod migration { - use super::*; - use frame_support::Blake2_256; - pub fn migrate() { - let current_index = >::current_index(); - let key_count = Keys::::get().len() as AuthIndex; - for i in 0..key_count { - ReceivedHeartbeats::migrate_keys::(current_index, i); - } - } -} - -impl MigrateAccount for Module { - fn migrate_account(a: &T::AccountId) { - use frame_support::Blake2_256; - let current_index = >::current_index(); - if let Ok(v) = a.using_encoded(|mut d| T::ValidatorId::decode(&mut d)) { - AuthoredBlocks::::migrate_keys::(current_index, v); - } - } -} - decl_module! { pub struct Module for enum Call where origin: T::Origin { type Error = Error; fn deposit_event() = default; - fn on_runtime_upgrade() { - migration::migrate::(); - } - + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn heartbeat( origin, heartbeat: Heartbeat, @@ -650,7 +626,10 @@ impl pallet_session::OneSessionHandler for Module { impl frame_support::unsigned::ValidateUnsigned for Module { type Call = Call; - fn validate_unsigned(call: &Self::Call) -> TransactionValidity { + fn validate_unsigned( + _source: TransactionSource, + call: &Self::Call, + ) -> TransactionValidity { if let Call::heartbeat(heartbeat, signature) = call { if >::is_online(heartbeat.authority_index) { // we already received a heartbeat for this authority diff --git a/frame/im-online/src/mock.rs b/frame/im-online/src/mock.rs index e0ee2547bf570681469c8027b3d1ec7a081077ec..3dc0543d8881b7d4154fe9895448038c99a1ad3b 100644 --- a/frame/im-online/src/mock.rs +++ b/frame/im-online/src/mock.rs @@ -119,7 +119,7 @@ impl frame_system::Trait for Runtime { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } @@ -141,6 +141,7 @@ impl pallet_session::Trait for Runtime { type Keys = UintAuthorityId; type Event = (); type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = pallet_session::PeriodicSessions; } impl pallet_session::historical::Trait for Runtime { @@ -174,7 +175,7 @@ pub type System = frame_system::Module; pub type Session = pallet_session::Module; pub fn advance_session() { - let now = System::block_number(); + let now = System::block_number().max(1); System::set_block_number(now + 1); Session::rotate_session(); assert_eq!(Session::current_index(), (now / Period::get()) as u32); diff --git a/frame/im-online/src/tests.rs b/frame/im-online/src/tests.rs index b43adca0fd485d36b5c8d2f8d81ce365d8518711..c7bf2afcca629f74765c1519abdd063e63dae1f1 100644 --- a/frame/im-online/src/tests.rs +++ b/frame/im-online/src/tests.rs @@ -190,7 +190,7 @@ fn late_heartbeat_should_fail() { #[test] fn should_generate_heartbeats() { - use sp_runtime::traits::OffchainWorker; + use frame_support::traits::OffchainWorker; let mut ext = new_test_ext(); let (offchain, _state) = TestOffchainExt::new(); diff --git a/frame/indices/Cargo.toml b/frame/indices/Cargo.toml index d7e01765b59d165a2f1f8d52e44e1e3b61ee18e4..f28f393642bedac28e1644f35046d6ad9c1b4a2e 100644 --- a/frame/indices/Cargo.toml +++ b/frame/indices/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-indices" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,17 +10,17 @@ description = "FRAME indices management pallet" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-keyring = { version = "2.0.0-alpha.2", optional = true, path = "../../primitives/keyring" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-keyring = { version = "2.0.0-alpha.5", optional = true, path = "../../primitives/keyring" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } [features] default = ["std"] @@ -35,3 +35,6 @@ std = [ "sp-runtime/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/indices/src/lib.rs b/frame/indices/src/lib.rs index 95ac6cf75283816bb3058f148d849f365de1345d..d2ba664d425ffce1ef0aac0745fea825d7183a42 100644 --- a/frame/indices/src/lib.rs +++ b/frame/indices/src/lib.rs @@ -25,6 +25,7 @@ use sp_runtime::traits::{ StaticLookup, Member, LookupError, Zero, One, BlakeTwo256, Hash, Saturating, AtLeast32Bit }; use frame_support::{Parameter, decl_module, decl_error, decl_event, decl_storage, ensure}; +use frame_support::weights::{Weight, SimpleDispatchInfo, WeighData}; use frame_support::dispatch::DispatchResult; use frame_support::traits::{Currency, ReservableCurrency, Get, BalanceStatus::Reserved}; use frame_support::storage::migration::take_storage_value; @@ -98,8 +99,10 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin, system = frame_system { fn deposit_event() = default; - fn on_initialize() { + fn on_initialize() -> Weight { Self::migrations(); + + SimpleDispatchInfo::default().weigh_data(()) } /// Assign an previously unassigned index. @@ -118,6 +121,7 @@ decl_module! { /// - One reserve operation. /// - One event. /// # + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn claim(origin, index: T::AccountIndex) { let who = ensure_signed(origin)?; @@ -145,6 +149,7 @@ decl_module! { /// - One transfer operation. /// - One event. /// # + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn transfer(origin, new: T::AccountId, index: T::AccountIndex) { let who = ensure_signed(origin)?; ensure!(who != new, Error::::NotTransfer); @@ -175,6 +180,7 @@ decl_module! { /// - One reserve operation. /// - One event. /// # + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn free(origin, index: T::AccountIndex) { let who = ensure_signed(origin)?; @@ -203,6 +209,7 @@ decl_module! { /// - Up to one reserve operation. /// - One event. /// # + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn force_transfer(origin, new: T::AccountId, index: T::AccountIndex) { ensure_root(origin)?; diff --git a/frame/indices/src/mock.rs b/frame/indices/src/mock.rs index f5cd1009e6619a9d4cc0a769249ef1074774c68b..355b3cc792c9456a0537c263d0f79a5ec58e7042 100644 --- a/frame/indices/src/mock.rs +++ b/frame/indices/src/mock.rs @@ -66,7 +66,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/membership/Cargo.toml b/frame/membership/Cargo.toml index b54109083dc0d524c127668573aea89c02b14c87..41e56b584f550332fe1b91f5763fa31765086d75 100644 --- a/frame/membership/Cargo.toml +++ b/frame/membership/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-membership" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,15 +10,15 @@ description = "FRAME membership management pallet" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } [features] default = ["std"] @@ -31,3 +31,6 @@ std = [ "frame-support/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index e9fa5a0630f0729995cb515f7b354f2fa0df06b4..129f3c4003bdd306a8feedcd4f2d39ffb6ddfa72 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -321,7 +321,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } ord_parameter_types! { diff --git a/frame/metadata/Cargo.toml b/frame/metadata/Cargo.toml index b3d333f369fbd901c5fa2d597fe3bedb063462ea..f965e1dddcb09687f4cdc193c62f09e60f4d12e5 100644 --- a/frame/metadata/Cargo.toml +++ b/frame/metadata/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-metadata" -version = "11.0.0-alpha.3" +version = "11.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,10 +9,10 @@ repository = "https://github.com/paritytech/substrate/" description = "Decodable variant of the RuntimeMetadata." [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } serde = { version = "1.0.101", optional = true, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } [features] default = ["std"] @@ -22,3 +22,6 @@ std = [ "sp-core/std", "serde", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/nicks/Cargo.toml b/frame/nicks/Cargo.toml index 07c84c439ffaa57dedb4e8a2da8647e9d02460df..ea88021d2528b8897c02a721ffc6fcf64564358d 100644 --- a/frame/nicks/Cargo.toml +++ b/frame/nicks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-nicks" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,16 +10,16 @@ description = "FRAME pallet for nick management" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } [features] default = ["std"] @@ -32,3 +32,6 @@ std = [ "frame-support/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/nicks/src/lib.rs b/frame/nicks/src/lib.rs index ddfd71107ff99dad440232132fc7e59ae0aac296..125d1fd1988657d47a40c183508f3945ddfc112d 100644 --- a/frame/nicks/src/lib.rs +++ b/frame/nicks/src/lib.rs @@ -171,6 +171,7 @@ decl_module! { /// - One storage read/write. /// - One event. /// # + #[weight = SimpleDispatchInfo::FixedNormal(70_000)] fn clear_name(origin) { let sender = ensure_signed(origin)?; @@ -286,7 +287,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } parameter_types! { diff --git a/frame/offences/Cargo.toml b/frame/offences/Cargo.toml index 99c52e40885378f8eccee76a75887ef216fdf38f..b858c03ba5ac0b58c038422e8d077ba7c278ddc6 100644 --- a/frame/offences/Cargo.toml +++ b/frame/offences/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-offences" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,18 +9,18 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME offences pallet" [dependencies] -pallet-balances = { version = "2.0.0-alpha.2", default-features = false, path = "../balances" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } +pallet-balances = { version = "2.0.0-alpha.5", default-features = false, path = "../balances" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } serde = { version = "1.0.101", optional = true } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/staking" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/staking" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -sp-io = { version = "2.0.0-alpha.2", path = "../../primitives/io" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +sp-io = { version = "2.0.0-alpha.5", path = "../../primitives/io" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } [features] default = ["std"] @@ -34,3 +34,6 @@ std = [ "frame-support/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/offences/src/lib.rs b/frame/offences/src/lib.rs index 325ceceb8b8855f4671cdbf145ed9b825c25d0b1..0ba7cd87f24552b9e14c5227b3683d1246b434f8 100644 --- a/frame/offences/src/lib.rs +++ b/frame/offences/src/lib.rs @@ -26,10 +26,12 @@ mod tests; use sp_std::vec::Vec; use frame_support::{ - decl_module, decl_event, decl_storage, Parameter, + decl_module, decl_event, decl_storage, Parameter, debug, + weights::{Weight, SimpleDispatchInfo, WeighData}, }; -use sp_runtime::traits::Hash; +use sp_runtime::{traits::Hash, Perbill}; use sp_staking::{ + SessionIndex, offence::{Offence, ReportOffence, Kind, OnOffenceHandler, OffenceDetails, OffenceError}, }; use codec::{Encode, Decode}; @@ -41,6 +43,13 @@ type OpaqueTimeSlot = Vec; /// A type alias for a report identifier. type ReportIdOf = ::Hash; +/// Type of data stored as a deferred offence +type DeferredOffenceOf = ( + Vec::AccountId, ::IdentificationTuple>>, + Vec, + SessionIndex, +); + /// Offences trait pub trait Trait: frame_system::Trait { /// The overarching event type. @@ -58,6 +67,10 @@ decl_storage! { map hasher(twox_64_concat) ReportIdOf => Option>; + /// Deferred reports that have been rejected by the offence handler and need to be submitted + /// at a later time. + DeferredOffences get(deferred_offences): Vec>; + /// A vector of reports of the same kind that happened at the same time slot. ConcurrentReportsIndex: double_map hasher(twox_64_concat) Kind, hasher(twox_64_concat) OpaqueTimeSlot @@ -76,20 +89,43 @@ decl_storage! { decl_event!( pub enum Event { /// 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. - Offence(Kind, OpaqueTimeSlot), + /// (kind-specific) time slot. This event is not deposited for duplicate slashes. last + /// element indicates of the offence was applied (true) or queued (false). + Offence(Kind, OpaqueTimeSlot, bool), } ); decl_module! { - /// Offences module, currently just responsible for taking offence reports. pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; - fn on_runtime_upgrade() { + fn on_runtime_upgrade() -> Weight { Reports::::remove_all(); ConcurrentReportsIndex::::remove_all(); ReportsByKindIndex::remove_all(); + + SimpleDispatchInfo::default().weigh_data(()) + } + + fn on_initialize(now: T::BlockNumber) -> Weight { + // only decode storage if we can actually submit anything again. + if T::OnOffenceHandler::can_report() { + >::mutate(|deferred| { + // keep those that fail to be reported again. An error log is emitted here; this + // should not happen if staking's `can_report` is implemented properly. + deferred.retain(|(o, p, s)| { + T::OnOffenceHandler::on_offence(&o, &p, *s).map_err(|_| { + debug::native::error!( + target: "pallet-offences", + "re-submitting a deferred slash returned Err at {}. This should not happen with pallet-staking", + now, + ); + }).is_err() + }) + }) + } + + SimpleDispatchInfo::default().weigh_data(()) } } } @@ -116,9 +152,6 @@ where None => return Err(OffenceError::DuplicateReport), }; - // Deposit the event. - Self::deposit_event(Event::Offence(O::ID, time_slot.encode())); - let offenders_count = concurrent_offenders.len() as u32; // The amount new offenders are slashed @@ -127,17 +160,42 @@ where let slash_perbill: Vec<_> = (0..concurrent_offenders.len()) .map(|_| new_fraction.clone()).collect(); - T::OnOffenceHandler::on_offence( + let applied = Self::report_or_store_offence( &concurrent_offenders, &slash_perbill, offence.session_index(), ); + // Deposit the event. + Self::deposit_event(Event::Offence(O::ID, time_slot.encode(), applied)); + Ok(()) } } impl Module { + /// Tries (without checking) to report an offence. Stores them in [`DeferredOffences`] in case + /// it fails. Returns false in case it has to store the offence. + fn report_or_store_offence( + concurrent_offenders: &[OffenceDetails], + slash_perbill: &[Perbill], + session_index: SessionIndex, + ) -> bool { + match T::OnOffenceHandler::on_offence( + &concurrent_offenders, + &slash_perbill, + session_index, + ) { + Ok(_) => true, + Err(_) => { + >::mutate(|d| + d.push((concurrent_offenders.to_vec(), slash_perbill.to_vec(), session_index)) + ); + false + } + } + } + /// Compute the ID for the given report properties. /// /// The report id depends on the offence kind, time slot and the id of offender. diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index c617224716115c44515f7336f61ae2d85bd773f1..e464200396ebde9be263ee75ea610813203d34c1 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -43,6 +43,7 @@ pub struct OnOffenceHandler; thread_local! { pub static ON_OFFENCE_PERBILL: RefCell> = RefCell::new(Default::default()); + pub static CAN_REPORT: RefCell = RefCell::new(true); } impl offence::OnOffenceHandler for OnOffenceHandler { @@ -50,11 +51,25 @@ impl offence::OnOffenceHandler for OnOff _offenders: &[OffenceDetails], slash_fraction: &[Perbill], _offence_session: SessionIndex, - ) { - ON_OFFENCE_PERBILL.with(|f| { - *f.borrow_mut() = slash_fraction.to_vec(); - }); + ) -> Result<(), ()> { + if >::can_report() { + ON_OFFENCE_PERBILL.with(|f| { + *f.borrow_mut() = slash_fraction.to_vec(); + }); + + Ok(()) + } else { + Err(()) + } } + + fn can_report() -> bool { + CAN_REPORT.with(|c| *c.borrow()) + } +} + +pub fn set_can_report(can_report: bool) { + CAN_REPORT.with(|c| *c.borrow_mut() = can_report); } pub fn with_on_offence_fractions) -> R>(f: F) -> R { @@ -90,7 +105,7 @@ impl frame_system::Trait for Runtime { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } @@ -113,7 +128,9 @@ impl_outer_event! { pub fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - t.into() + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext } /// Offences module. diff --git a/frame/offences/src/tests.rs b/frame/offences/src/tests.rs index 0ed98427c65f8eba4d02be1e679dc1e8db52d791..3179a0752318f1340ae924599734423b793fb012 100644 --- a/frame/offences/src/tests.rs +++ b/frame/offences/src/tests.rs @@ -21,9 +21,10 @@ use super::*; use crate::mock::{ Offences, System, Offence, TestEvent, KIND, new_test_ext, with_on_offence_fractions, - offence_reports, + offence_reports, set_can_report, }; use sp_runtime::Perbill; +use frame_support::traits::OnInitialize; use frame_system::{EventRecord, Phase}; #[test] @@ -129,8 +130,8 @@ fn should_deposit_event() { assert_eq!( System::events(), vec![EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode())), + phase: Phase::Initialization, + event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode(), true)), topics: vec![], }] ); @@ -164,8 +165,8 @@ fn doesnt_deposit_event_for_dups() { assert_eq!( System::events(), vec![EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode())), + phase: Phase::Initialization, + event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode(), true)), topics: vec![], }] ); @@ -212,3 +213,54 @@ fn should_properly_count_offences() { ); }); } + +#[test] +fn should_queue_and_resubmit_rejected_offence() { + new_test_ext().execute_with(|| { + set_can_report(false); + + // will get deferred + let offence = Offence { + validator_set_count: 5, + time_slot: 42, + offenders: vec![5], + }; + Offences::report_offence(vec![], offence).unwrap(); + assert_eq!(Offences::deferred_offences().len(), 1); + // event also indicates unapplied. + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::offences(crate::Event::Offence(KIND, 42u128.encode(), false)), + topics: vec![], + }] + ); + + // will not dequeue + Offences::on_initialize(2); + + // again + let offence = Offence { + validator_set_count: 5, + time_slot: 62, + offenders: vec![5], + }; + Offences::report_offence(vec![], offence).unwrap(); + assert_eq!(Offences::deferred_offences().len(), 2); + + set_can_report(true); + + // can be submitted + let offence = Offence { + validator_set_count: 5, + time_slot: 72, + offenders: vec![5], + }; + Offences::report_offence(vec![], offence).unwrap(); + assert_eq!(Offences::deferred_offences().len(), 2); + + Offences::on_initialize(3); + assert_eq!(Offences::deferred_offences().len(), 0); + }) +} diff --git a/frame/randomness-collective-flip/Cargo.toml b/frame/randomness-collective-flip/Cargo.toml index 4e4ce76fee7edb7d39402be261c078925f63bb79..acd1c216884c036715f0f52b997cb0b78d5c6669 100644 --- a/frame/randomness-collective-flip/Cargo.toml +++ b/frame/randomness-collective-flip/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-randomness-collective-flip" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,15 +10,15 @@ description = "FRAME randomness collective flip pallet" [dependencies] safe-mix = { version = "1.0", default-features = false } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-io = { version = "2.0.0-alpha.2", path = "../../primitives/io" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-io = { version = "2.0.0-alpha.5", path = "../../primitives/io" } [features] default = ["std"] @@ -30,3 +30,6 @@ std = [ "sp-runtime/std", "sp-std/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/randomness-collective-flip/src/lib.rs b/frame/randomness-collective-flip/src/lib.rs index 525bbedb404a424ad5a5ba8ec514af773fd702ff..fdc465b4dc3bb0e9163bc750595a290138a7b24d 100644 --- a/frame/randomness-collective-flip/src/lib.rs +++ b/frame/randomness-collective-flip/src/lib.rs @@ -35,12 +35,13 @@ //! ### Example - Get random seed for the current block //! //! ``` -//! use frame_support::{decl_module, dispatch, traits::Randomness}; +//! use frame_support::{decl_module, dispatch, traits::Randomness, weights::SimpleDispatchInfo}; //! //! pub trait Trait: frame_system::Trait {} //! //! decl_module! { //! pub struct Module for enum Call where origin: T::Origin { +//! #[weight = SimpleDispatchInfo::default()] //! pub fn random_module_example(origin) -> dispatch::DispatchResult { //! let _random_seed = >::random_seed(); //! Ok(()) @@ -54,7 +55,10 @@ use sp_std::{prelude::*, convert::TryInto}; use sp_runtime::traits::Hash; -use frame_support::{decl_module, decl_storage, traits::Randomness}; +use frame_support::{ + decl_module, decl_storage, traits::Randomness, + weights::{Weight, SimpleDispatchInfo, WeighData} +}; use safe_mix::TripletMix; use codec::Encode; use frame_system::Trait; @@ -69,7 +73,7 @@ fn block_number_to_index(block_number: T::BlockNumber) -> usize { decl_module! { pub struct Module for enum Call where origin: T::Origin { - fn on_initialize(block_number: T::BlockNumber) { + fn on_initialize(block_number: T::BlockNumber) -> Weight { let parent_hash = >::parent_hash(); >::mutate(|ref mut values| if values.len() < RANDOM_MATERIAL_LEN as usize { @@ -78,6 +82,8 @@ decl_module! { let index = block_number_to_index::(block_number); values[index] = parent_hash; }); + + SimpleDispatchInfo::default().weigh_data(()) } } } @@ -156,9 +162,11 @@ mod tests { use sp_runtime::{ Perbill, testing::Header, - traits::{BlakeTwo256, OnInitialize, Header as _, IdentityLookup}, + traits::{BlakeTwo256, Header as _, IdentityLookup}, + }; + use frame_support::{ + impl_outer_origin, parameter_types, weights::Weight, traits::{Randomness, OnInitialize}, }; - use frame_support::{impl_outer_origin, parameter_types, weights::Weight, traits::Randomness}; #[derive(Clone, PartialEq, Eq)] pub struct Test; @@ -192,7 +200,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/recovery/Cargo.toml b/frame/recovery/Cargo.toml index 80456aa375dd2e569300aac339642f5124930397..3347014f6e8cafc53a28626aad5124e35fa25b6a 100644 --- a/frame/recovery/Cargo.toml +++ b/frame/recovery/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-recovery" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,17 +10,17 @@ description = "FRAME account recovery pallet" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } enumflags2 = { version = "0.6.2" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } [features] default = ["std"] @@ -33,3 +33,6 @@ std = [ "frame-support/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/recovery/src/lib.rs b/frame/recovery/src/lib.rs index d995606e525ace4469699ff9c4f8737675238701..c59ba85bdc5012306b3a002be7e454615fceb4d2 100644 --- a/frame/recovery/src/lib.rs +++ b/frame/recovery/src/lib.rs @@ -163,7 +163,6 @@ use frame_support::{ traits::{Currency, ReservableCurrency, Get, BalanceStatus}, }; use frame_system::{self as system, ensure_signed, ensure_root}; -use frame_support::traits::MigrateAccount; #[cfg(test)] mod mock; @@ -258,13 +257,6 @@ decl_storage! { } } -impl MigrateAccount for Module { - fn migrate_account(a: &T::AccountId) { - Recoverable::::migrate_key_from_blake(a); - Proxy::::migrate_key_from_blake(a); - } -} - decl_event! { /// Events type. pub enum Event where @@ -653,6 +645,7 @@ decl_module! { /// # /// - One storage mutation to check account is recovered by `who`. O(1) /// # + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn cancel_recovered(origin, account: T::AccountId) { let who = ensure_signed(origin)?; // Check `who` is allowed to make a call on behalf of `account` diff --git a/frame/recovery/src/mock.rs b/frame/recovery/src/mock.rs index 3eaba0d6c15bf29963a86c7bdfbc6e210bf1604f..9327ece572212af48e739eac3725925592c9bc95 100644 --- a/frame/recovery/src/mock.rs +++ b/frame/recovery/src/mock.rs @@ -21,12 +21,13 @@ use super::*; use frame_support::{ impl_outer_origin, impl_outer_dispatch, impl_outer_event, parameter_types, weights::Weight, + traits::{OnInitialize, OnFinalize}, }; use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. use sp_runtime::{ - Perbill, traits::{BlakeTwo256, IdentityLookup, OnInitialize, OnFinalize}, testing::Header, + Perbill, traits::{BlakeTwo256, IdentityLookup}, testing::Header, }; use crate as recovery; @@ -79,7 +80,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/recovery/src/tests.rs b/frame/recovery/src/tests.rs index 9c644291c906dcbf00ab0185d3e1065fc8d54fb1..fb993043a5b005f4e94976f99a9701af6ab83b79 100644 --- a/frame/recovery/src/tests.rs +++ b/frame/recovery/src/tests.rs @@ -240,7 +240,7 @@ fn initiate_recovery_works() { assert_eq!(Balances::reserved_balance(1), 10); // Recovery status object is created correctly let recovery_status = ActiveRecovery { - created: 1, + created: 0, deposit: 10, friends: vec![], }; @@ -288,7 +288,7 @@ fn vouch_recovery_works() { assert_ok!(Recovery::vouch_recovery(Origin::signed(3), 5, 1)); // Final recovery status object is updated correctly let recovery_status = ActiveRecovery { - created: 1, + created: 0, deposit: 10, friends: vec![2, 3, 4], }; diff --git a/frame/scored-pool/Cargo.toml b/frame/scored-pool/Cargo.toml index a1b7b39d8cd9ffc2b548522168b0599ca5958678..b878c5bb47559d3d8eab8cb842d8eb00182abed7 100644 --- a/frame/scored-pool/Cargo.toml +++ b/frame/scored-pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-scored-pool" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,17 +9,17 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for scored pools" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } serde = { version = "1.0.101", optional = true } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } [features] default = ["std"] @@ -32,3 +32,6 @@ std = [ "frame-support/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/scored-pool/src/lib.rs b/frame/scored-pool/src/lib.rs index ebabe791055c62f1c18ad84fb97b30359e84ecc4..2c2bfc6a12bb8a206a8fea905d524f3fe7706dc7 100644 --- a/frame/scored-pool/src/lib.rs +++ b/frame/scored-pool/src/lib.rs @@ -61,6 +61,7 @@ //! //! decl_module! { //! pub struct Module for enum Call where origin: T::Origin { +//! #[weight = frame_support::weights::SimpleDispatchInfo::default()] //! pub fn candidate(origin) -> dispatch::DispatchResult { //! let who = ensure_signed(origin)?; //! @@ -96,6 +97,7 @@ use sp_std::{ use frame_support::{ decl_module, decl_storage, decl_event, ensure, decl_error, traits::{ChangeMembers, InitializeMembers, Currency, Get, ReservableCurrency}, + weights::{Weight, SimpleDispatchInfo, WeighData}, }; use frame_system::{self as system, ensure_root, ensure_signed}; use sp_runtime::{ @@ -245,11 +247,12 @@ decl_module! { /// Every `Period` blocks the `Members` set is refreshed from the /// highest scoring members in the pool. - fn on_initialize(n: T::BlockNumber) { + fn on_initialize(n: T::BlockNumber) -> Weight { if n % T::Period::get() == Zero::zero() { let pool = >::get(); >::refresh_members(pool, ChangeReceiver::MembershipChanged); } + SimpleDispatchInfo::default().weigh_data(()) } /// Add `origin` to the pool of candidates. @@ -263,6 +266,7 @@ decl_module! { /// /// The `index` parameter of this function must be set to /// the index of the transactor in the `Pool`. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn submit_candidacy(origin) { let who = ensure_signed(origin)?; ensure!(!>::contains_key(&who), Error::::AlreadyInPool); @@ -292,6 +296,7 @@ decl_module! { /// /// The `index` parameter of this function must be set to /// the index of the transactor in the `Pool`. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn withdraw_candidacy( origin, index: u32 @@ -311,6 +316,7 @@ decl_module! { /// /// The `index` parameter of this function must be set to /// the index of `dest` in the `Pool`. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn kick( origin, dest: ::Source, @@ -335,6 +341,7 @@ decl_module! { /// /// The `index` parameter of this function must be set to /// the index of the `dest` in the `Pool`. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn score( origin, dest: ::Source, @@ -375,6 +382,7 @@ decl_module! { /// (this happens each `Period`). /// /// May only be called from root. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn change_member_count(origin, count: u32) { ensure_root(origin)?; >::put(&count); diff --git a/frame/scored-pool/src/mock.rs b/frame/scored-pool/src/mock.rs index f50e918ee6ae6c8a4a48417a02821ffb36611be2..a28b7891370eafbdc47260ef602ae2a69660c119 100644 --- a/frame/scored-pool/src/mock.rs +++ b/frame/scored-pool/src/mock.rs @@ -71,7 +71,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/scored-pool/src/tests.rs b/frame/scored-pool/src/tests.rs index 4b21339505ded6b55d9a520595af2df1c8277fca..8d87a20f757b223443b2b134fb1d66f0ab793b53 100644 --- a/frame/scored-pool/src/tests.rs +++ b/frame/scored-pool/src/tests.rs @@ -19,8 +19,8 @@ use super::*; use mock::*; -use frame_support::{assert_ok, assert_noop}; -use sp_runtime::traits::{OnInitialize, BadOrigin}; +use frame_support::{assert_ok, assert_noop, traits::OnInitialize}; +use sp_runtime::traits::BadOrigin; type ScoredPool = Module; type System = frame_system::Module; diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index 74ca9fe67bb41968927f5050a7db90466c2bc6e4..f12a8b4a71f2fc8df7f81ac50098c32ffd9cd7aa 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-session" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,20 +10,20 @@ description = "FRAME sessions pallet" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/staking" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -pallet-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../timestamp" } -sp-trie = { optional = true, path = "../../primitives/trie", default-features = false , version = "2.0.0-alpha.2"} -sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.2"} +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/staking" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +pallet-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../timestamp" } +sp-trie = { optional = true, path = "../../primitives/trie", default-features = false, version = "2.0.0-alpha.5"} +sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.5"} impl-trait-for-tuples = "0.1.3" [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-application-crypto = { version = "2.0.0-alpha.2", path = "../../primitives/application-crypto" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-application-crypto = { version = "2.0.0-alpha.5", path = "../../primitives/application-crypto" } lazy_static = "1.4.0" [features] @@ -40,3 +40,6 @@ std = [ "sp-trie/std", "sp-io/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/session/benchmarking/Cargo.toml b/frame/session/benchmarking/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..181fb37bfdbc10025ba7d9a1baa1840662c3fe79 --- /dev/null +++ b/frame/session/benchmarking/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "pallet-session-benchmarking" +version = "2.0.0-alpha.5" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME sessions pallet benchmarking" + +[dependencies] +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/runtime" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../../system" } +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../../benchmarking" } +pallet-staking = { version = "2.0.0-alpha.5", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } +pallet-session = { version = "2.0.0-alpha.5", default-features = false, path = "../../session" } + +[features] +default = ["std"] +std = [ + "sp-std/std", + "sp-runtime/std", + "frame-system/std", + "frame-benchmarking/std", + "pallet-staking/std", + "pallet-session/std", +] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/session/benchmarking/src/lib.rs b/frame/session/benchmarking/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..db925bd72ee45e204eca8faeee4a97d0d4d6a09d --- /dev/null +++ b/frame/session/benchmarking/src/lib.rs @@ -0,0 +1,57 @@ +// Copyright 2020 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 . + +//! Benchmarks for the Session Pallet. +// This is separated into its own crate due to cyclic dependency issues. + +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_std::prelude::*; +use sp_std::vec; + +use frame_system::RawOrigin; +use frame_benchmarking::benchmarks; + +use pallet_session::*; +use pallet_session::Module as Session; + +use pallet_staking::{ + MAX_NOMINATIONS, + benchmarking::create_validator_with_nominators, +}; + +pub struct Module(pallet_session::Module); + +pub trait Trait: pallet_session::Trait + pallet_staking::Trait {} + +benchmarks! { + _ { } + + set_keys { + let n in 1 .. MAX_NOMINATIONS as u32; + let validator = create_validator_with_nominators::(n, MAX_NOMINATIONS as u32)?; + let keys = T::Keys::default(); + let proof: Vec = vec![0,1,2,3]; + }: _(RawOrigin::Signed(validator), keys, proof) + + purge_keys { + let n in 1 .. MAX_NOMINATIONS as u32; + let validator = create_validator_with_nominators::(n, MAX_NOMINATIONS as u32)?; + let keys = T::Keys::default(); + let proof: Vec = vec![0,1,2,3]; + Session::::set_keys(RawOrigin::Signed(validator.clone()).into(), keys, proof)?; + }: _(RawOrigin::Signed(validator)) +} diff --git a/frame/session/src/historical.rs b/frame/session/src/historical.rs index 848f6d55994bcbda425ac93acae606cf2dec6341..f9990dd1e8a7a716f4a557467fe5364602c45fe3 100644 --- a/frame/session/src/historical.rs +++ b/frame/session/src/historical.rs @@ -67,25 +67,7 @@ decl_storage! { } decl_module! { - pub struct Module for enum Call where origin: T::Origin { - fn on_initialize(_n: T::BlockNumber) { - CachedObsolete::::remove_all(); - } - fn on_runtime_upgrade() { - migration::migrate::(); - } - } -} - -mod migration { - use super::*; - pub fn migrate() { - if let Some((begin, end)) = StoredRange::get() { - for i in begin..end { - HistoricalSessions::::migrate_key_from_blake(i); - } - } - } + pub struct Module for enum Call where origin: T::Origin {} } impl Module { @@ -328,12 +310,12 @@ impl> frame_support::traits::KeyOwnerProofSystem<(KeyTy mod tests { use super::*; use sp_core::crypto::key_types::DUMMY; - use sp_runtime::{traits::OnInitialize, testing::UintAuthorityId}; + use sp_runtime::testing::UintAuthorityId; use crate::mock::{ NEXT_VALIDATORS, force_new_session, set_next_validators, Test, System, Session, }; - use frame_support::traits::KeyOwnerProofSystem; + use frame_support::traits::{KeyOwnerProofSystem, OnInitialize}; type Historical = Module; diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index e98ea2475d070a4b12408215edc36e2a3822a9b6..9346b060fa48a8afd22d01dc1d88d6a6b743a8e4 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -102,17 +102,22 @@ use sp_std::{prelude::*, marker::PhantomData, ops::{Sub, Rem}}; use codec::Decode; use sp_runtime::{KeyTypeId, Perbill, RuntimeAppPublic, BoundToRuntimeAppPublic}; -use frame_support::weights::SimpleDispatchInfo; -use sp_runtime::traits::{Convert, Zero, Member, OpaqueKeys}; +use sp_runtime::traits::{Convert, Zero, Member, OpaqueKeys, Saturating}; use sp_staking::SessionIndex; -use frame_support::{ensure, decl_module, decl_event, decl_storage, decl_error, ConsensusEngineId}; -use frame_support::{traits::{Get, FindAuthor, ValidatorRegistration}, Parameter}; -use frame_support::dispatch::{self, DispatchResult, DispatchError}; +use frame_support::{ + ensure, decl_module, decl_event, decl_storage, decl_error, ConsensusEngineId, Parameter, + traits::{ + Get, FindAuthor, ValidatorRegistration, EstimateNextSessionRotation, EstimateNextNewSession, + }, + dispatch::{self, DispatchResult, DispatchError}, + weights::{Weight, SimpleDispatchInfo, WeighData}, +}; use frame_system::{self as system, ensure_signed}; -use frame_support::traits::MigrateAccount; #[cfg(test)] mod mock; +#[cfg(test)] +mod tests; #[cfg(feature = "historical")] pub mod historical; @@ -144,6 +149,29 @@ impl< } } +impl< + BlockNumber: Rem + Sub + Zero + PartialOrd + Saturating + Clone, + Period: Get, + Offset: Get, +> EstimateNextSessionRotation for PeriodicSessions { + fn estimate_next_session_rotation(now: BlockNumber) -> Option { + let offset = Offset::get(); + let period = Period::get(); + Some(if now > offset { + let block_after_last_session = (now.clone() - offset) % period.clone(); + if block_after_last_session > Zero::zero() { + now.saturating_add( + period.saturating_sub(block_after_last_session) + ) + } else { + Zero::zero() + } + } else { + offset + }) + } +} + /// A trait for managing creation of new validator set. pub trait SessionManager { /// Plan a new session, and optionally provide the new validator set. @@ -327,6 +355,11 @@ pub trait Trait: frame_system::Trait { /// 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; @@ -466,7 +499,7 @@ decl_module! { /// In this case, purge_keys will need to be called before the account can be removed. /// # #[weight = SimpleDispatchInfo::FixedNormal(150_000)] - fn set_keys(origin, keys: T::Keys, proof: Vec) -> dispatch::DispatchResult { + pub fn set_keys(origin, keys: T::Keys, proof: Vec) -> dispatch::DispatchResult { let who = ensure_signed(origin)?; ensure!(keys.ownership_proof_is_valid(&proof), Error::::InvalidProof); @@ -487,29 +520,19 @@ decl_module! { /// - Reduces system account refs by one on success. /// # #[weight = SimpleDispatchInfo::FixedNormal(150_000)] - fn purge_keys(origin) { + pub fn purge_keys(origin) { 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) { + fn on_initialize(n: T::BlockNumber) -> Weight { if T::ShouldEndSession::should_end_session(n) { Self::rotate_session(); } - } - } -} -impl MigrateAccount for Module { - fn migrate_account(a: &T::AccountId) { - if let Some(v) = T::ValidatorIdOf::convert(a.clone()) { - if let Some(keys) = NextKeys::::migrate_key_from_blake(v) { - for id in T::Keys::key_ids() { - KeyOwner::::migrate_key_from_blake((*id, keys.get_raw(*id))); - } - } + SimpleDispatchInfo::default().weigh_data(()) } } } @@ -743,307 +766,10 @@ impl> FindAuthor } } -#[cfg(test)] -mod tests { - use super::*; - use frame_support::assert_ok; - use sp_core::crypto::key_types::DUMMY; - use sp_runtime::{traits::OnInitialize, testing::UintAuthorityId}; - use mock::{ - NEXT_VALIDATORS, SESSION_CHANGED, TEST_SESSION_CHANGED, authorities, force_new_session, - set_next_validators, set_session_length, session_changed, Test, Origin, System, Session, - reset_before_session_end_called, before_session_end_called, - }; - - fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - GenesisConfig:: { - keys: NEXT_VALIDATORS.with(|l| - l.borrow().iter().cloned().map(|i| (i, i, UintAuthorityId(i).into())).collect() - ), - }.assimilate_storage(&mut t).unwrap(); - sp_io::TestExternalities::new(t) - } - - fn initialize_block(block: u64) { - SESSION_CHANGED.with(|l| *l.borrow_mut() = false); - System::set_block_number(block); - Session::on_initialize(block); - } - - #[test] - fn simple_setup_should_work() { - new_test_ext().execute_with(|| { - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - assert_eq!(Session::validators(), vec![1, 2, 3]); - }); - } - - #[test] - fn put_get_keys() { - new_test_ext().execute_with(|| { - Session::put_keys(&10, &UintAuthorityId(10).into()); - assert_eq!(Session::load_keys(&10), Some(UintAuthorityId(10).into())); - }) - } - - #[test] - fn keys_cleared_on_kill() { - let mut ext = new_test_ext(); - ext.execute_with(|| { - assert_eq!(Session::validators(), vec![1, 2, 3]); - assert_eq!(Session::load_keys(&1), Some(UintAuthorityId(1).into())); - - let id = DUMMY; - assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), Some(1)); - - assert!(!System::allow_death(&1)); - assert_ok!(Session::purge_keys(Origin::signed(1))); - assert!(System::allow_death(&1)); - - assert_eq!(Session::load_keys(&1), None); - assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), None); - }) - } - - #[test] - fn authorities_should_track_validators() { - reset_before_session_end_called(); - - new_test_ext().execute_with(|| { - set_next_validators(vec![1, 2]); - force_new_session(); - initialize_block(1); - assert_eq!(Session::queued_keys(), vec![ - (1, UintAuthorityId(1).into()), - (2, UintAuthorityId(2).into()), - ]); - assert_eq!(Session::validators(), vec![1, 2, 3]); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - force_new_session(); - initialize_block(2); - assert_eq!(Session::queued_keys(), vec![ - (1, UintAuthorityId(1).into()), - (2, UintAuthorityId(2).into()), - ]); - assert_eq!(Session::validators(), vec![1, 2]); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - set_next_validators(vec![1, 2, 4]); - assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(4).into(), vec![])); - force_new_session(); - initialize_block(3); - assert_eq!(Session::queued_keys(), vec![ - (1, UintAuthorityId(1).into()), - (2, UintAuthorityId(2).into()), - (4, UintAuthorityId(4).into()), - ]); - assert_eq!(Session::validators(), vec![1, 2]); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]); - assert!(before_session_end_called()); - - force_new_session(); - initialize_block(4); - assert_eq!(Session::queued_keys(), vec![ - (1, UintAuthorityId(1).into()), - (2, UintAuthorityId(2).into()), - (4, UintAuthorityId(4).into()), - ]); - assert_eq!(Session::validators(), vec![1, 2, 4]); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(4)]); - }); - } - - #[test] - fn should_work_with_early_exit() { - new_test_ext().execute_with(|| { - set_session_length(10); - - initialize_block(1); - assert_eq!(Session::current_index(), 0); - - initialize_block(2); - assert_eq!(Session::current_index(), 0); - - force_new_session(); - initialize_block(3); - assert_eq!(Session::current_index(), 1); - - initialize_block(9); - assert_eq!(Session::current_index(), 1); - - initialize_block(10); - assert_eq!(Session::current_index(), 2); - }); - } - - #[test] - fn session_change_should_work() { - new_test_ext().execute_with(|| { - // Block 1: No change - initialize_block(1); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - - // Block 2: Session rollover, but no change. - initialize_block(2); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - - // 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_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - - // Block 4: Session rollover; no visible change. - initialize_block(4); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - - // Block 5: No change. - initialize_block(5); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - - // Block 6: Session rollover; authority 2 changes. - initialize_block(6); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(5), UintAuthorityId(3)]); - }); - } - - #[test] - fn duplicates_are_not_allowed() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - Session::on_initialize(1); - assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]).is_err()); - assert!(Session::set_keys(Origin::signed(1), UintAuthorityId(10).into(), vec![]).is_ok()); - - // is fine now that 1 has migrated off. - assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]).is_ok()); - }); - } - - #[test] - fn session_changed_flag_works() { - reset_before_session_end_called(); - - new_test_ext().execute_with(|| { - TEST_SESSION_CHANGED.with(|l| *l.borrow_mut() = true); - - force_new_session(); - initialize_block(1); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - force_new_session(); - initialize_block(2); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - Session::disable_index(0); - force_new_session(); - initialize_block(3); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - force_new_session(); - initialize_block(4); - assert!(session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - force_new_session(); - initialize_block(5); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5).into(), vec![])); - force_new_session(); - initialize_block(6); - assert!(!session_changed()); - assert!(before_session_end_called()); - 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![])); - force_new_session(); - initialize_block(7); - assert!(session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - // while changing the keys of a non-validator does not. - force_new_session(); - initialize_block(7); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - }); - } - - #[test] - fn periodic_session_works() { - struct Period; - struct Offset; - - impl Get for Period { - fn get() -> u64 { 10 } - } - - impl Get for Offset { - fn get() -> u64 { 3 } - } - - - type P = PeriodicSessions; - - for i in 0..3 { - assert!(!P::should_end_session(i)); - } - - assert!(P::should_end_session(3)); - - for i in (1..10).map(|i| 3 + i) { - assert!(!P::should_end_session(i)); - } - - assert!(P::should_end_session(13)); - } - - #[test] - 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), - ::Keys::decode(&mut &new_keys[..]).expect("Decode keys"), - vec![], - ) - ); - }); - } - - #[test] - fn return_true_if_more_than_third_is_disabled() { - new_test_ext().execute_with(|| { - set_next_validators(vec![1, 2, 3, 4, 5, 6, 7]); - force_new_session(); - initialize_block(1); - // apply the new validator set - force_new_session(); - initialize_block(2); - - 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); - }); +impl EstimateNextNewSession for Module { + /// 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 { + T::NextSessionRotation::estimate_next_session_rotation(now) } } diff --git a/frame/session/src/mock.rs b/frame/session/src/mock.rs index 6a60233222510c566918b503e22f265d0f417016..dd28d357491dafb1a7f39e8195a75ea8117bad84 100644 --- a/frame/session/src/mock.rs +++ b/frame/session/src/mock.rs @@ -179,7 +179,7 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } @@ -205,6 +205,7 @@ impl Trait for Test { type Keys = MockSessionKeys; type Event = (); type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = (); } #[cfg(feature = "historical")] diff --git a/frame/session/src/tests.rs b/frame/session/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..4e95d91cc7ef8e35b0e94327cc002d1de944f43e --- /dev/null +++ b/frame/session/src/tests.rs @@ -0,0 +1,319 @@ +// Copyright 2017-2020 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 . + +// Tests for the Session Pallet + +use super::*; +use frame_support::{traits::OnInitialize, assert_ok}; +use sp_core::crypto::key_types::DUMMY; +use sp_runtime::testing::UintAuthorityId; +use mock::{ + NEXT_VALIDATORS, SESSION_CHANGED, TEST_SESSION_CHANGED, authorities, force_new_session, + set_next_validators, set_session_length, session_changed, Test, Origin, System, Session, + reset_before_session_end_called, before_session_end_called, +}; + +fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + GenesisConfig:: { + keys: NEXT_VALIDATORS.with(|l| + l.borrow().iter().cloned().map(|i| (i, i, UintAuthorityId(i).into())).collect() + ), + }.assimilate_storage(&mut t).unwrap(); + sp_io::TestExternalities::new(t) +} + +fn initialize_block(block: u64) { + SESSION_CHANGED.with(|l| *l.borrow_mut() = false); + System::set_block_number(block); + Session::on_initialize(block); +} + +#[test] +fn simple_setup_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + assert_eq!(Session::validators(), vec![1, 2, 3]); + }); +} + +#[test] +fn put_get_keys() { + new_test_ext().execute_with(|| { + Session::put_keys(&10, &UintAuthorityId(10).into()); + assert_eq!(Session::load_keys(&10), Some(UintAuthorityId(10).into())); + }) +} + +#[test] +fn keys_cleared_on_kill() { + let mut ext = new_test_ext(); + ext.execute_with(|| { + assert_eq!(Session::validators(), vec![1, 2, 3]); + assert_eq!(Session::load_keys(&1), Some(UintAuthorityId(1).into())); + + let id = DUMMY; + assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), Some(1)); + + assert!(!System::allow_death(&1)); + assert_ok!(Session::purge_keys(Origin::signed(1))); + assert!(System::allow_death(&1)); + + assert_eq!(Session::load_keys(&1), None); + assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), None); + }) +} + +#[test] +fn authorities_should_track_validators() { + reset_before_session_end_called(); + + new_test_ext().execute_with(|| { + set_next_validators(vec![1, 2]); + force_new_session(); + initialize_block(1); + assert_eq!(Session::queued_keys(), vec![ + (1, UintAuthorityId(1).into()), + (2, UintAuthorityId(2).into()), + ]); + assert_eq!(Session::validators(), vec![1, 2, 3]); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + force_new_session(); + initialize_block(2); + assert_eq!(Session::queued_keys(), vec![ + (1, UintAuthorityId(1).into()), + (2, UintAuthorityId(2).into()), + ]); + assert_eq!(Session::validators(), vec![1, 2]); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + set_next_validators(vec![1, 2, 4]); + assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(4).into(), vec![])); + force_new_session(); + initialize_block(3); + assert_eq!(Session::queued_keys(), vec![ + (1, UintAuthorityId(1).into()), + (2, UintAuthorityId(2).into()), + (4, UintAuthorityId(4).into()), + ]); + assert_eq!(Session::validators(), vec![1, 2]); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]); + assert!(before_session_end_called()); + + force_new_session(); + initialize_block(4); + assert_eq!(Session::queued_keys(), vec![ + (1, UintAuthorityId(1).into()), + (2, UintAuthorityId(2).into()), + (4, UintAuthorityId(4).into()), + ]); + assert_eq!(Session::validators(), vec![1, 2, 4]); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(4)]); + }); +} + +#[test] +fn should_work_with_early_exit() { + new_test_ext().execute_with(|| { + set_session_length(10); + + initialize_block(1); + assert_eq!(Session::current_index(), 0); + + initialize_block(2); + assert_eq!(Session::current_index(), 0); + + force_new_session(); + initialize_block(3); + assert_eq!(Session::current_index(), 1); + + initialize_block(9); + assert_eq!(Session::current_index(), 1); + + initialize_block(10); + assert_eq!(Session::current_index(), 2); + }); +} + +#[test] +fn session_change_should_work() { + new_test_ext().execute_with(|| { + // Block 1: No change + initialize_block(1); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + + // Block 2: Session rollover, but no change. + initialize_block(2); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + + // 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_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + + // Block 4: Session rollover; no visible change. + initialize_block(4); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + + // Block 5: No change. + initialize_block(5); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + + // Block 6: Session rollover; authority 2 changes. + initialize_block(6); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(5), UintAuthorityId(3)]); + }); +} + +#[test] +fn duplicates_are_not_allowed() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + Session::on_initialize(1); + assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]).is_err()); + assert!(Session::set_keys(Origin::signed(1), UintAuthorityId(10).into(), vec![]).is_ok()); + + // is fine now that 1 has migrated off. + assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]).is_ok()); + }); +} + +#[test] +fn session_changed_flag_works() { + reset_before_session_end_called(); + + new_test_ext().execute_with(|| { + TEST_SESSION_CHANGED.with(|l| *l.borrow_mut() = true); + + force_new_session(); + initialize_block(1); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + force_new_session(); + initialize_block(2); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + Session::disable_index(0); + force_new_session(); + initialize_block(3); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + force_new_session(); + initialize_block(4); + assert!(session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + force_new_session(); + initialize_block(5); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5).into(), vec![])); + force_new_session(); + initialize_block(6); + assert!(!session_changed()); + assert!(before_session_end_called()); + 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![])); + force_new_session(); + initialize_block(7); + assert!(session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + // while changing the keys of a non-validator does not. + force_new_session(); + initialize_block(7); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + }); +} + +#[test] +fn periodic_session_works() { + struct Period; + struct Offset; + + impl Get for Period { + fn get() -> u64 { 10 } + } + + impl Get for Offset { + fn get() -> u64 { 3 } + } + + + type P = PeriodicSessions; + + for i in 0..3 { + assert!(!P::should_end_session(i)); + } + + assert!(P::should_end_session(3)); + + for i in (1..10).map(|i| 3 + i) { + assert!(!P::should_end_session(i)); + } + + assert!(P::should_end_session(13)); +} + +#[test] +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), + ::Keys::decode(&mut &new_keys[..]).expect("Decode keys"), + vec![], + ) + ); + }); +} + +#[test] +fn return_true_if_more_than_third_is_disabled() { + new_test_ext().execute_with(|| { + set_next_validators(vec![1, 2, 3, 4, 5, 6, 7]); + force_new_session(); + initialize_block(1); + // apply the new validator set + force_new_session(); + initialize_block(2); + + 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); + }); +} diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index 35b1c5c4a45159464b783ac201955b81ebd0d260..be419fb63fe2d0800dde5802e9ce1cf02de52b14 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-society" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,17 +10,17 @@ description = "FRAME society pallet" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.2"} -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.5"} +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } rand_chacha = { version = "0.2", default-features = false } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } [features] default = ["std"] @@ -34,3 +34,10 @@ std = [ "frame-support/std", "frame-system/std", ] +runtime-benchmarks = [ + "sp-runtime/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index eafd5ec51998d9073ab02b9db48e49b1de1583a4..49f48697f906fa33082079d25adafc7dff4a6c40 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -260,8 +260,11 @@ use sp_runtime::{Percent, ModuleId, RuntimeDebug, } }; use frame_support::{decl_error, decl_module, decl_storage, decl_event, ensure, dispatch::DispatchResult}; -use frame_support::weights::SimpleDispatchInfo; -use frame_support::traits::{Currency, ReservableCurrency, Randomness, Get, ChangeMembers, BalanceStatus, ExistenceRequirement::AllowDeath, MigrateAccount}; +use frame_support::weights::{SimpleDispatchInfo, Weight, WeighData}; +use frame_support::traits::{ + Currency, ReservableCurrency, Randomness, Get, ChangeMembers, BalanceStatus, + ExistenceRequirement::AllowDeath +}; use frame_system::{self as system, ensure_signed, ensure_root}; type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; @@ -1025,7 +1028,7 @@ decl_module! { Self::deposit_event(RawEvent::NewMaxMembers(max)); } - fn on_initialize(n: T::BlockNumber) { + fn on_initialize(n: T::BlockNumber) -> Weight { let mut members = vec![]; // Run a candidate/membership rotation @@ -1042,6 +1045,8 @@ decl_module! { } Self::rotate_challenge(&mut members); } + + SimpleDispatchInfo::default().weigh_data(()) } } } @@ -1130,12 +1135,6 @@ decl_event! { } } -impl MigrateAccount for Module { - fn migrate_account(a: &T::AccountId) { - Payouts::::migrate_key_from_blake(a); - } -} - /// Simple ensure origin struct to filter for the founder account. pub struct EnsureFounder(sp_std::marker::PhantomData); impl EnsureOrigin for EnsureFounder { @@ -1146,6 +1145,12 @@ impl EnsureOrigin for EnsureFounder { (r, _) => Err(T::Origin::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)) + } } /// Pick an item at pseudo-random from the slice, given the `rng`. `None` iff the slice is empty. diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index b80e0cd2bfcc132790b922b59f06c7fdf95d19d3..a66a5e6e047d44230b35f62fcf1a78043b5e1085 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -18,14 +18,16 @@ use super::*; -use frame_support::{impl_outer_origin, parameter_types, ord_parameter_types}; +use frame_support::{ + impl_outer_origin, parameter_types, ord_parameter_types, traits::{OnInitialize, OnFinalize} +}; use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. use sp_runtime::{ Perbill, testing::Header, - traits::{BlakeTwo256, IdentityLookup, OnInitialize, OnFinalize}, + traits::{BlakeTwo256, IdentityLookup}, }; use frame_system::EnsureSignedBy; @@ -77,7 +79,7 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); type AccountData = pallet_balances::AccountData; } diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index ef4bb60a29e6a593d1c2f4560edbe6dd73537b43..228b7b60f8200612750a3705e48ed798700f67d6 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-staking" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,31 +10,50 @@ description = "FRAME pallet staking" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-keyring = { version = "2.0.0-alpha.2", optional = true, path = "../../primitives/keyring" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-phragmen = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/phragmen" } -sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.2"} -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/staking" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -pallet-session = { version = "2.0.0-alpha.2", features = ["historical"], path = "../session", default-features = false } -pallet-authorship = { version = "2.0.0-alpha.2", default-features = false, path = "../authorship" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-phragmen = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/phragmen" } +sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.5"} +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/staking" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +pallet-session = { version = "2.0.0-alpha.5", features = ["historical"], path = "../session", default-features = false } +pallet-authorship = { version = "2.0.0-alpha.5", default-features = false, path = "../authorship" } +sp-application-crypto = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/application-crypto" } +static_assertions = "1.1.0" + +# Optional imports for tesing-utils feature +pallet-indices = { version = "2.0.0-alpha.4", optional = true, path = "../indices", default-features = false } +sp-core = { version = "2.0.0-alpha.4", optional = true, path = "../../primitives/core", default-features = false } +rand = { version = "0.7.3", optional = true, default-features = false } + +# Optional imports for benchmarking +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } +rand_chacha = { version = "0.2", default-features = false, optional = true } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } -pallet-timestamp = { version = "2.0.0-alpha.2", path = "../timestamp" } -pallet-staking-reward-curve = { version = "2.0.0-alpha.2", path = "../staking/reward-curve" } -substrate-test-utils = { version = "2.0.0-alpha.2", path = "../../test-utils" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } +pallet-timestamp = { version = "2.0.0-alpha.5", path = "../timestamp" } +pallet-staking-reward-curve = { version = "2.0.0-alpha.5", path = "../staking/reward-curve" } +substrate-test-utils = { version = "2.0.0-alpha.5", path = "../../test-utils" } +frame-benchmarking = { version = "2.0.0-alpha.5", path = "../benchmarking" } +rand_chacha = { version = "0.2" } +parking_lot = "0.10.0" +env_logger = "0.7.1" +hex = "0.4" [features] -migrate = [] +testing-utils = [ + "std", + "pallet-indices/std", + "sp-core/std", + "rand/std", +] default = ["std"] std = [ "serde", - "sp-keyring", "codec/std", "sp-std/std", "sp-phragmen/std", @@ -45,4 +64,13 @@ std = [ "pallet-session/std", "frame-system/std", "pallet-authorship/std", + "sp-application-crypto/std", + "sp-core/std", ] +runtime-benchmarks = [ + "rand_chacha", + "frame-benchmarking", +] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/staking/fuzz/.gitignore b/frame/staking/fuzz/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..572e03bdf321b6cc3a99488183436905cefd086d --- /dev/null +++ b/frame/staking/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/frame/staking/fuzz/Cargo.lock b/frame/staking/fuzz/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..f6e8cfa08d6675144979b3a5ef10eaa8f9dcf0d3 --- /dev/null +++ b/frame/staking/fuzz/Cargo.lock @@ -0,0 +1,2189 @@ +# 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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16971f2f0ce65c5cf2a1546cc6a0af102ecb11e265ddaa9433fb3e5bfdf676a4" + +[[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.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17b52e737c40a7d75abca20b29a19a0eb7ba9fc72c5a72dd282a0a3c2c0dc35" +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 = "bitmask" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da9b3d9f6f585199287a473f4f8dfab6566cf827d15c00c219f53c645687ead" + +[[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 = "bumpalo" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" + +[[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 = "c2-chacha" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" +dependencies = [ + "ppv-lite86", +] + +[[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 = "frame-benchmarking" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "sp-api", + "sp-io", + "sp-runtime", + "sp-runtime-interface", + "sp-std", +] + +[[package]] +name = "frame-metadata" +version = "11.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "serde", + "sp-core", + "sp-std", +] + +[[package]] +name = "frame-support" +version = "2.0.0-alpha.3" +dependencies = [ + "bitmask", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "log", + "once_cell", + "parity-scale-codec", + "paste", + "serde", + "sp-arithmetic", + "sp-core", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "tracing", +] + +[[package]] +name = "frame-support-procedural" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support-procedural-tools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "2.0.0-alpha.3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-system" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "impl-trait-for-tuples", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" + +[[package]] +name = "futures-executor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" + +[[package]] +name = "futures-macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" + +[[package]] +name = "futures-task" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" + +[[package]] +name = "futures-util" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[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 = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[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 = "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 = "js-sys" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cb931d43e71f560c81badb0191596562bafad2be06a3f9025b845c847c60df5" +dependencies = [ + "wasm-bindgen", +] + +[[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 = "libfuzzer-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb789afcc589a08928d1e466087445ab740a0f70a2ee23d9349a0e3723d65e1b" +dependencies = [ + "arbitrary", + "cc", +] + +[[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 = "memory-db" +version = "0.19.0" +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 = "pallet-authorship" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-authorship", + "sp-core", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-balances" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "serde", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-indices" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-session" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "pallet-timestamp", + "parity-scale-codec", + "serde", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-staking" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "frame-system", + "pallet-authorship", + "pallet-indices", + "pallet-session", + "parity-scale-codec", + "rand 0.7.3", + "serde", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-phragmen", + "sp-runtime", + "sp-staking", + "sp-std", + "static_assertions", +] + +[[package]] +name = "pallet-staking-fuzz" +version = "0.0.0" +dependencies = [ + "frame-support", + "frame-system", + "libfuzzer-sys", + "pallet-balances", + "pallet-indices", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "parity-scale-codec", + "rand 0.7.3", + "sp-core", + "sp-io", + "sp-phragmen", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-staking-reward-curve" +version = "2.0.0-alpha.3" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pallet-timestamp" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "serde", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[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 = "pin-utils" +version = "0.1.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" + +[[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-macro-nested" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" + +[[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.1", + "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +dependencies = [ + "c2-chacha", + "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 = "send_wrapper" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eddf2e8f50ced781f288c19f18621fa72a3779e3cb58dbf23b07469b0abeb4" + +[[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 = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[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-api" +version = "2.0.0-alpha.3" +dependencies = [ + "hash-db", + "parity-scale-codec", + "sp-api-proc-macro", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-version", +] + +[[package]] +name = "sp-api-proc-macro" +version = "2.0.0-alpha.3" +dependencies = [ + "blake2-rfc", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[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-authorship" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "sp-inherents", + "sp-runtime", + "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-keyring" +version = "2.0.0-alpha.3" +dependencies = [ + "lazy_static", + "sp-core", + "sp-runtime", + "strum", +] + +[[package]] +name = "sp-panic-handler" +version = "2.0.0-alpha.3" +dependencies = [ + "backtrace", + "log", +] + +[[package]] +name = "sp-phragmen" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "serde", + "sp-core", + "sp-phragmen-compact", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-phragmen-compact" +version = "2.0.0-dev" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[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-staking" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "sp-runtime", + "sp-std", +] + +[[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-timestamp" +version = "2.0.0-alpha.3" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-api", + "sp-inherents", + "sp-runtime", + "sp-std", + "wasm-timer", +] + +[[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-version" +version = "2.0.0-alpha.3" +dependencies = [ + "impl-serde 0.2.3", + "parity-scale-codec", + "serde", + "sp-runtime", + "sp-std", +] + +[[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 = "strum" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[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 = "tracing" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1721cc8cf7d770cc4257872507180f35a4797272f5962f24c806af9e7faf52ab" +dependencies = [ + "cfg-if", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbad39da2f9af1cae3016339ad7f2c7a9e870f12e8fd04c4fd7ef35b30c0d2b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83a9a47081cd522c09c81b31aec2c9273424976f922ad61c053b58350b715" +dependencies = [ + "lazy_static", +] + +[[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-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[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 = "wasm-bindgen" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3557c397ab5a8e347d434782bcd31fc1483d927a6826804cec05cc792ee2519d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457414a91863c0ec00090dba537f88ab955d93ca6555862c29b6d860990b8a8a" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6fde1d36e75a714b5fe0cffbb78978f222ea6baebb726af13c78869fdb4205" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bda4168030a6412ea8a047e27238cadf56f0e53516e1e83fec0a8b7c786f6d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc9f36ad51f25b0219a3d4d13b90eb44cd075dff8b6280cca015775d7acaddd8" + +[[package]] +name = "wasm-timer" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324c5e65a08699c9c4334ba136597ab22b85dccd4b65dd1e36ccf8f723a95b54" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.9.0", + "pin-utils", + "send_wrapper", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[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 = "web-sys" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721c6263e2c66fd44501cc5efbfa2b7dfa775d13e4ea38c46299646ed1f9c70a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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/frame/staking/fuzz/Cargo.toml b/frame/staking/fuzz/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a78fbf17dc8a3a1dd3c8572167d8d1d27692059a --- /dev/null +++ b/frame/staking/fuzz/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "pallet-staking-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.3" +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +pallet-staking = { version = "2.0.0-alpha.2", path = "..", features = ["testing-utils"] } +pallet-staking-reward-curve = { version = "2.0.0-alpha.2", path = "../reward-curve" } +pallet-session = { version = "2.0.0-alpha.2", path = "../../session" } +pallet-indices = { version = "2.0.0-alpha.2", path = "../../indices" } +pallet-balances = { version = "2.0.0-alpha.2", path = "../../balances" } +pallet-timestamp = { version = "2.0.0-alpha.2", path = "../../timestamp" } +frame-system = { version = "2.0.0-alpha.2", path = "../../system" } +frame-support = { version = "2.0.0-alpha.2", path = "../../support" } +sp-std = { version = "2.0.0-alpha.2", path = "../../../primitives/std" } +sp-io ={ version = "2.0.0-alpha.2", path = "../../../primitives/io" } +sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } +sp-phragmen = { version = "2.0.0-alpha.2", path = "../../../primitives/phragmen" } +sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } +rand = "0.7.3" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "submit_solution" +path = "fuzz_targets/submit_solution.rs" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/staking/fuzz/fuzz_targets/mock.rs b/frame/staking/fuzz/fuzz_targets/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..4bb3437f92368b35b7bfd2ef4f4529214972e6ca --- /dev/null +++ b/frame/staking/fuzz/fuzz_targets/mock.rs @@ -0,0 +1,182 @@ +// Copyright 2020 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 . + +//! Mock file for staking fuzzing. + +use sp_runtime::traits::{Convert, SaturatedConversion}; +use frame_support::{impl_outer_origin, impl_outer_dispatch, parameter_types}; + +type AccountId = u64; +type AccountIndex = u32; +type BlockNumber = u64; +type Balance = u64; + +type System = frame_system::Module; +type Balances = pallet_balances::Module; +type Staking = pallet_staking::Module; +type Indices = pallet_indices::Module; +type Session = pallet_session::Module; + +impl_outer_origin! { + pub enum Origin for Test where system = frame_system {} +} + +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + staking::Staking, + } +} + +pub struct CurrencyToVoteHandler; +impl Convert for CurrencyToVoteHandler { + fn convert(x: u64) -> u64 { + x + } +} +impl Convert for CurrencyToVoteHandler { + fn convert(x: u128) -> u64 { + x.saturated_into() + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct Test; + +impl frame_system::Trait for Test { + 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 = (); + type BlockHashCount = (); + type MaximumBlockWeight = (); + type AvailableBlockRatio = (); + type MaximumBlockLength = (); + type Version = (); + type ModuleToIndex = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (Balances,); +} +parameter_types! { + pub const ExistentialDeposit: Balance = 10; +} +impl pallet_balances::Trait for Test { + type Balance = Balance; + type Event = (); + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} +impl pallet_indices::Trait for Test { + type AccountIndex = AccountIndex; + type Event = (); + type Currency = Balances; + type Deposit = (); +} +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} +impl pallet_timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; +} +impl pallet_session::historical::Trait 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::Trait 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 = (); + type ValidatorId = AccountId; + type ValidatorIdOf = pallet_staking::StashOf; + type DisabledValidatorsThreshold = (); +} +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 type Extrinsic = sp_runtime::testing::TestXt; +type SubmitTransaction = frame_system::offchain::TransactionSubmitter< + sp_runtime::testing::UintAuthorityId, + Test, + Extrinsic, +>; + +impl pallet_staking::Trait for Test { + type Currency = Balances; + type Time = pallet_timestamp::Module; + type CurrencyToVote = CurrencyToVoteHandler; + type RewardRemainder = (); + type Event = (); + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = (); + type SessionInterface = Self; + type RewardCurve = RewardCurve; + type NextNewSession = Session; + type ElectionLookahead = (); + type Call = Call; + type SubmitTransaction = SubmitTransaction; + type KeyType = sp_runtime::testing::UintAuthorityId; + type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; +} diff --git a/frame/staking/fuzz/fuzz_targets/submit_solution.rs b/frame/staking/fuzz/fuzz_targets/submit_solution.rs new file mode 100644 index 0000000000000000000000000000000000000000..5d1fcf1d7ea850dd033b89e41514cd672848ab24 --- /dev/null +++ b/frame/staking/fuzz/fuzz_targets/submit_solution.rs @@ -0,0 +1,130 @@ +// Copyright 2020 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 . + +//! Fuzzing for staking pallet. + +#![no_main] +use libfuzzer_sys::fuzz_target; +use mock::Test; +use pallet_staking::testing_utils::{ + self, USER, get_seq_phragmen_solution, get_weak_solution, setup_chain_stakers, + set_validator_count, signed_account, +}; +use frame_support::assert_ok; +use sp_runtime::{traits::Dispatchable, DispatchError}; + +mod mock; + +#[repr(u32)] +#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +enum Mode { + /// Initial submission. This will be rather cheap. + InitialSubmission, + /// A better submission that will replace the previous ones. This is the most expensive. + StrongerSubmission, + /// A weak submission that will be rejected. This will be rather cheap. + WeakerSubmission, +} + +pub fn new_test_ext() -> Result { + frame_system::GenesisConfig::default().build_storage::().map(Into::into) +} + +fuzz_target!(|do_reduce: bool| { + let ext = new_test_ext(); + let mode: Mode = unsafe { std::mem::transmute(testing_utils::random(0, 2)) }; + let num_validators = testing_utils::random(50, 500); + let num_nominators = testing_utils::random(200, 2000); + let edge_per_voter = testing_utils::random(1, 16); + let to_elect = testing_utils::random(10, num_validators); + + println!("+++ instance with params {} / {} / {} / {:?} / {}", + num_nominators, + num_validators, + edge_per_voter, + mode, + to_elect, + ); + + ext.unwrap_or_default().execute_with(|| { + // initial setup + set_validator_count::(to_elect); + setup_chain_stakers::( + num_validators, + num_nominators, + edge_per_voter, + ); + + println!("++ Chain setup done."); + + // stuff to submit + let (winners, compact, score) = match mode { + Mode::InitialSubmission => { + /* No need to setup anything */ + get_seq_phragmen_solution::(do_reduce) + }, + Mode::StrongerSubmission => { + let (winners, compact, score) = get_weak_solution::(false); + assert_ok!( + >::submit_election_solution( + signed_account::(USER), + winners, + compact, + score, + ) + ); + get_seq_phragmen_solution::(do_reduce) + }, + Mode::WeakerSubmission => { + let (winners, compact, score) = get_seq_phragmen_solution::(do_reduce); + assert_ok!( + >::submit_election_solution( + signed_account::(USER), + winners, + compact, + score, + ) + ); + get_weak_solution::(false) + } + }; + + println!("++ Submission ready."); + + // must have chosen correct number of winners. + assert_eq!(winners.len() as u32, >::validator_count()); + + // final call and origin + let call = pallet_staking::Call::::submit_election_solution( + winners, + compact, + score, + ); + let caller = signed_account::(USER); + + // actually submit + match mode { + Mode::WeakerSubmission => { + assert_eq!( + call.dispatch(caller.into()).unwrap_err(), + DispatchError::Module { index: 0, error: 11, message: Some("PhragmenWeakSubmission") }, + ); + }, + _ => assert_ok!(call.dispatch(caller.into())), + }; + }) +}); diff --git a/frame/staking/reward-curve/Cargo.toml b/frame/staking/reward-curve/Cargo.toml index d55813682e62192a28d8c68992524fea41d8aa6d..b3b749e96cd51fa050dfb6233f0c99eadf6ad3fb 100644 --- a/frame/staking/reward-curve/Cargo.toml +++ b/frame/staking/reward-curve/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-staking-reward-curve" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -18,4 +18,7 @@ proc-macro2 = "1.0.6" proc-macro-crate = "0.1.4" [dev-dependencies] -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/staking/reward-curve/src/log.rs b/frame/staking/reward-curve/src/log.rs index 1a25dbb98628bd1b526fea508158490945d2a6da..e0929a95970139450c0e953f6df7e8849e5ae727 100644 --- a/frame/staking/reward-curve/src/log.rs +++ b/frame/staking/reward-curve/src/log.rs @@ -14,13 +14,13 @@ pub fn log2(p: u32, q: u32) -> u32 { } let mut n = 0u32; - while !(p >= 2u32.pow(n)*q) || !(p < 2u32.pow(n+1)*q) { + while !(p >= (1u32 << n)*q) || !(p < (1u32 << (n+1))*q) { n += 1; } - assert!(p < 2u32.pow(n+1) * q); + assert!(p < (1u32 << (n+1)) * q); - let y_num: u32 = (p - 2u32.pow(n) * q).try_into().unwrap(); - let y_den: u32 = (p + 2u32.pow(n) * q).try_into().unwrap(); + let y_num: u32 = (p - (1u32 << n) * q).try_into().unwrap(); + let y_den: u32 = (p + (1u32 << n) * q).try_into().unwrap(); let _2_div_ln_2 = 2_885_390u32; diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..a88c00c144dd56f0130aec98f6b9c2bc27a11610 --- /dev/null +++ b/frame/staking/src/benchmarking.rs @@ -0,0 +1,464 @@ +// Copyright 2020 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 . + +//! Staking pallet benchmarking. + +use super::*; + +use rand_chacha::{rand_core::{RngCore, SeedableRng}, ChaChaRng}; + +use sp_runtime::traits::One; +use sp_io::hashing::blake2_256; + +use frame_system::RawOrigin; +use frame_benchmarking::{benchmarks, account}; + +use crate::Module as Staking; +use frame_system::Module as System; + +const SEED: u32 = 0; + +fn create_funded_user(string: &'static str, n: u32) -> T::AccountId { + let user = account(string, n, SEED); + let balance = T::Currency::minimum_balance() * 100.into(); + T::Currency::make_free_balance_be(&user, balance); + user +} + +pub fn create_stash_controller(n: u32) -> Result<(T::AccountId, T::AccountId), &'static str> { + let stash = create_funded_user::("stash", n); + let controller = create_funded_user::("controller", n); + let controller_lookup: ::Source = T::Lookup::unlookup(controller.clone()); + let reward_destination = RewardDestination::Staked; + let amount = T::Currency::minimum_balance() * 10.into(); + Staking::::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, amount, reward_destination)?; + return Ok((stash, controller)) +} + +fn create_validators(max: u32) -> Result::Source>, &'static str> { + let mut validators: Vec<::Source> = Vec::with_capacity(max as usize); + for i in 0 .. max { + let (stash, controller) = create_stash_controller::(i)?; + let validator_prefs = ValidatorPrefs { + commission: Perbill::from_percent(50), + }; + Staking::::validate(RawOrigin::Signed(controller).into(), validator_prefs)?; + let stash_lookup: ::Source = T::Lookup::unlookup(stash); + validators.push(stash_lookup); + } + Ok(validators) +} + +// This function generates v validators and n nominators who are randomly nominating up to MAX_NOMINATIONS. +pub fn create_validators_with_nominators_for_era(v: u32, n: u32) -> Result<(), &'static str> { + let mut validators: Vec<::Source> = Vec::with_capacity(v as usize); + let mut rng = ChaChaRng::from_seed(SEED.using_encoded(blake2_256)); + + // Create v validators + for i in 0 .. v { + let (v_stash, v_controller) = create_stash_controller::(i)?; + let validator_prefs = ValidatorPrefs { + commission: Perbill::from_percent(50), + }; + Staking::::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?; + let stash_lookup: ::Source = T::Lookup::unlookup(v_stash.clone()); + validators.push(stash_lookup.clone()); + } + + // Create n nominators + for j in 0 .. n { + let (_n_stash, n_controller) = create_stash_controller::(u32::max_value() - j)?; + + // Have them randomly validate + let mut available_validators = validators.clone(); + let mut selected_validators: Vec<::Source> = Vec::with_capacity(MAX_NOMINATIONS); + for _ in 0 .. v.min(MAX_NOMINATIONS as u32) { + let selected = rng.next_u32() as usize % available_validators.len(); + let validator = available_validators.remove(selected); + selected_validators.push(validator); + } + Staking::::nominate(RawOrigin::Signed(n_controller.clone()).into(), selected_validators)?; + } + + ValidatorCount::put(v); + + Ok(()) +} + +// This function generates one validator being nominated by n nominators. +// It starts an era and creates pending payouts. +pub fn create_validator_with_nominators(n: u32, upper_bound: u32) -> Result { + let mut points_total = 0; + let mut points_individual = Vec::new(); + + MinimumValidatorCount::put(0); + + let (v_stash, v_controller) = create_stash_controller::(0)?; + let validator_prefs = ValidatorPrefs { + commission: Perbill::from_percent(50), + }; + Staking::::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?; + let stash_lookup: ::Source = T::Lookup::unlookup(v_stash.clone()); + + points_total += 10; + points_individual.push((v_stash, 10)); + + // Give the validator n nominators, but keep total users in the system the same. + for i in 0 .. upper_bound { + let (_n_stash, n_controller) = create_stash_controller::(u32::max_value() - i)?; + if i < n { + Staking::::nominate(RawOrigin::Signed(n_controller.clone()).into(), vec![stash_lookup.clone()])?; + } + } + + ValidatorCount::put(1); + + // Start a new Era + let new_validators = Staking::::new_era(SessionIndex::one()).unwrap(); + + assert!(new_validators.len() == 1); + + // Give Era Points + let reward = EraRewardPoints:: { + total: points_total, + individual: points_individual.into_iter().collect(), + }; + + let current_era = CurrentEra::get().unwrap(); + ErasRewardPoints::::insert(current_era, reward); + + // Create reward pool + let total_payout = T::Currency::minimum_balance() * 1000.into(); + >::insert(current_era, total_payout); + + Ok(v_controller) +} + +// This function generates one nominator nominating v validators. +// It starts an era and creates pending payouts. +pub fn create_nominator_with_validators(v: u32) -> Result<(T::AccountId, Vec), &'static str> { + let mut validators = Vec::new(); + let mut points_total = 0; + let mut points_individual = Vec::new(); + + MinimumValidatorCount::put(0); + + // Create v validators + let mut validator_lookups = Vec::new(); + for i in 0 .. v { + let (v_stash, v_controller) = create_stash_controller::(i)?; + let validator_prefs = ValidatorPrefs { + commission: Perbill::from_percent(50), + }; + Staking::::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?; + let stash_lookup: ::Source = T::Lookup::unlookup(v_stash.clone()); + + points_total += 10; + points_individual.push((v_stash.clone(), 10)); + validator_lookups.push(stash_lookup); + // Add to the list if it is less than the number we want the nominator to have + if validators.len() < v as usize { + validators.push(v_stash.clone()) + } + } + + // Create a nominator + let (_n_stash, n_controller) = create_stash_controller::(u32::max_value())?; + Staking::::nominate(RawOrigin::Signed(n_controller.clone()).into(), validator_lookups)?; + + ValidatorCount::put(v); + + // Start a new Era + let new_validators = Staking::::new_era(SessionIndex::one()).unwrap(); + + assert!(new_validators.len() == v as usize); + + // Give Era Points + let reward = EraRewardPoints:: { + total: points_total, + individual: points_individual.into_iter().collect(), + }; + + let current_era = CurrentEra::get().unwrap(); + ErasRewardPoints::::insert(current_era, reward); + + // Create reward pool + let total_payout = T::Currency::minimum_balance() * 1000.into(); + >::insert(current_era, total_payout); + + Ok((n_controller, validators)) +} + +benchmarks! { + _{ + // User account seed + let u in 0 .. 1000 => (); + } + + bond { + let u in ...; + let stash = create_funded_user::("stash",u); + let controller = create_funded_user::("controller", u); + let controller_lookup: ::Source = T::Lookup::unlookup(controller); + let reward_destination = RewardDestination::Staked; + let amount = T::Currency::minimum_balance() * 10.into(); + }: _(RawOrigin::Signed(stash), controller_lookup, amount, reward_destination) + + bond_extra { + let u in ...; + let (stash, _) = create_stash_controller::(u)?; + let max_additional = T::Currency::minimum_balance() * 10.into(); + }: _(RawOrigin::Signed(stash), max_additional) + + unbond { + let u in ...; + let (_, controller) = create_stash_controller::(u)?; + let amount = T::Currency::minimum_balance() * 10.into(); + }: _(RawOrigin::Signed(controller), amount) + + // Worst case scenario, everything is removed after the bonding duration + withdraw_unbonded { + let u in ...; + let (stash, controller) = create_stash_controller::(u)?; + let amount = T::Currency::minimum_balance() * 10.into(); + Staking::::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?; + let current_block = System::::block_number(); + // let unbond_block = current_block + T::BondingDuration::get().into() + 10.into(); + // System::::set_block_number(unbond_block); + }: _(RawOrigin::Signed(controller)) + + validate { + let u in ...; + let (_, controller) = create_stash_controller::(u)?; + let prefs = ValidatorPrefs::default(); + }: _(RawOrigin::Signed(controller), prefs) + + // Worst case scenario, MAX_NOMINATIONS + nominate { + let n in 1 .. MAX_NOMINATIONS as u32; + let (_, controller) = create_stash_controller::(n + 1)?; + let validators = create_validators::(n)?; + }: _(RawOrigin::Signed(controller), validators) + + chill { + let u in ...; + let (_, controller) = create_stash_controller::(u)?; + }: _(RawOrigin::Signed(controller)) + + set_payee { + let u in ...; + let (_, controller) = create_stash_controller::(u)?; + }: _(RawOrigin::Signed(controller), RewardDestination::Controller) + + set_controller { + let u in ...; + let (stash, _) = create_stash_controller::(u)?; + let new_controller = create_funded_user::("new_controller", u); + let new_controller_lookup = T::Lookup::unlookup(new_controller); + }: _(RawOrigin::Signed(stash), new_controller_lookup) + + set_validator_count { + let c in 0 .. 1000; + }: _(RawOrigin::Root, c) + + force_no_eras { let i in 1 .. 1; }: _(RawOrigin::Root) + + force_new_era {let i in 1 .. 1; }: _(RawOrigin::Root) + + force_new_era_always { let i in 1 .. 1; }: _(RawOrigin::Root) + + // Worst case scenario, the list of invulnerables is very long. + set_invulnerables { + let v in 0 .. 1000; + let mut invulnerables = Vec::new(); + for i in 0 .. v { + invulnerables.push(account("invulnerable", i, SEED)); + } + }: _(RawOrigin::Root, invulnerables) + + force_unstake { + let u in ...; + let (stash, _) = create_stash_controller::(u)?; + }: _(RawOrigin::Root, stash) + + cancel_deferred_slash { + let s in 1 .. 1000; + let mut unapplied_slashes = Vec::new(); + let era = EraIndex::one(); + for _ in 0 .. 1000 { + unapplied_slashes.push(UnappliedSlash::>::default()); + } + UnappliedSlashes::::insert(era, &unapplied_slashes); + + let slash_indices: Vec = (0 .. s).collect(); + }: _(RawOrigin::Root, era, slash_indices) + + payout_validator { + let n in 1 .. MAX_NOMINATIONS as u32; + let validator = create_validator_with_nominators::(n, MAX_NOMINATIONS as u32)?; + let current_era = CurrentEra::get().unwrap(); + }: _(RawOrigin::Signed(validator), current_era) + + payout_nominator { + let v in 0 .. MAX_NOMINATIONS as u32; + let (nominator, validators) = create_nominator_with_validators::(v)?; + let current_era = CurrentEra::get().unwrap(); + let find_nominator = validators.into_iter().map(|x| (x, 0)).collect(); + }: _(RawOrigin::Signed(nominator), current_era, find_nominator) + + rebond { + let l in 1 .. 1000; + let (_, controller) = create_stash_controller::(u)?; + let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); + let unlock_chunk = UnlockChunk::> { + value: 1.into(), + era: EraIndex::zero(), + }; + for _ in 0 .. l { + staking_ledger.unlocking.push(unlock_chunk.clone()) + } + Ledger::::insert(controller.clone(), staking_ledger); + }: _(RawOrigin::Signed(controller), (l + 100).into()) + + 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()) + + reap_stash { + let u in 1 .. 1000; + let (stash, controller) = create_stash_controller::(u)?; + T::Currency::make_free_balance_be(&stash, 0.into()); + }: _(RawOrigin::Signed(controller), stash) + + new_era { + let v in 1 .. 10; + let n in 1 .. 100; + MinimumValidatorCount::put(0); + create_validators_with_nominators_for_era::(v, n)?; + let session_index = SessionIndex::one(); + }: { + let validators = Staking::::new_era(session_index).ok_or("`new_era` failed")?; + assert!(validators.len() == v as usize); + } + + do_slash { + let l in 1 .. 1000; + let (stash, controller) = create_stash_controller::(0)?; + let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); + let unlock_chunk = UnlockChunk::> { + value: 1.into(), + era: EraIndex::zero(), + }; + for _ in 0 .. l { + staking_ledger.unlocking.push(unlock_chunk.clone()) + } + Ledger::::insert(controller.clone(), staking_ledger.clone()); + let slash_amount = T::Currency::minimum_balance() * 10.into(); + }: { + crate::slashing::do_slash::( + &stash, + slash_amount, + &mut BalanceOf::::zero(), + &mut NegativeImbalanceOf::::zero() + ); + } +} + +#[cfg(test)] +mod tests { + use crate::*; + use crate::mock::*; + use frame_support::assert_ok; + + use crate::benchmarking::{ + create_validators_with_nominators_for_era, + create_validator_with_nominators, + create_nominator_with_validators, + }; + + #[test] + fn create_validators_with_nominators_for_era_works() { + ExtBuilder::default().has_stakers(false).build().execute_with(|| { + let v = 10; + let n = 100; + + create_validators_with_nominators_for_era::(v,n).unwrap(); + + let count_validators = Validators::::iter().count(); + let count_nominators = Nominators::::iter().count(); + + assert_eq!(count_validators, v as usize); + assert_eq!(count_nominators, n as usize); + }); + } + + #[test] + fn create_validator_with_nominators_works() { + ExtBuilder::default().has_stakers(false).build().execute_with(|| { + let n = 10; + + let validator = create_validator_with_nominators::( + n, + MAX_NOMINATIONS as u32, + ).unwrap(); + + let current_era = CurrentEra::get().unwrap(); + let controller = validator; + let ledger = Staking::ledger(&controller).unwrap(); + let stash = &ledger.stash; + + let original_free_balance = Balances::free_balance(stash); + assert_ok!(Staking::payout_validator(Origin::signed(controller), current_era)); + let new_free_balance = Balances::free_balance(stash); + + assert!(original_free_balance < new_free_balance); + }); + } + + #[test] + fn create_nominator_with_validators_works() { + ExtBuilder::default().has_stakers(false).build().execute_with(|| { + let v = 5; + + let (nominator, validators) = create_nominator_with_validators::(v).unwrap(); + + let current_era = CurrentEra::get().unwrap(); + let controller = nominator; + let ledger = Staking::ledger(&controller).unwrap(); + let stash = &ledger.stash; + + let find_nominator = validators.into_iter().map(|x| (x, 0)).collect(); + + let original_free_balance = Balances::free_balance(stash); + assert_ok!(Staking::payout_nominator(Origin::signed(controller), current_era, find_nominator)); + let new_free_balance = Balances::free_balance(stash); + + assert!(original_free_balance < new_free_balance); + }); + } + +} diff --git a/frame/staking/src/inflation.rs b/frame/staking/src/inflation.rs index e75ac3af2bf332cc7f357807da33d386732d12f0..d20741d9bc44d656134bfe9297268f41ea3b52b8 100644 --- a/frame/staking/src/inflation.rs +++ b/frame/staking/src/inflation.rs @@ -19,7 +19,7 @@ //! The staking rate in NPoS is the total amount of tokens staked by nominators and validators, //! divided by the total token supply. -use sp_runtime::{Perbill, PerThing, traits::AtLeast32Bit, curve::PiecewiseLinear}; +use sp_runtime::{Perbill, traits::AtLeast32Bit, curve::PiecewiseLinear}; /// The total payout to all validators (and their nominators) per era. /// diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 517069a3f034857bf7d2362dfa219b31d22bdae0..b19caec258fa5cd6c3614ac4ab6f78d51db33aa7 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -72,8 +72,8 @@ //! There are three possible roles that any staked account pair can be in: `Validator`, `Nominator` //! and `Idle` (defined in [`StakerStatus`](./enum.StakerStatus.html)). There are three //! corresponding instructions to change between roles, namely: -//! [`validate`](./enum.Call.html#variant.validate), [`nominate`](./enum.Call.html#variant.nominate), -//! and [`chill`](./enum.Call.html#variant.chill). +//! [`validate`](./enum.Call.html#variant.validate), +//! [`nominate`](./enum.Call.html#variant.nominate), and [`chill`](./enum.Call.html#variant.chill). //! //! #### Validating //! @@ -157,6 +157,7 @@ //! decl_module! { //! pub struct Module for enum Call where origin: T::Origin { //! /// Reward a validator. +//! #[weight = frame_support::weights::SimpleDispatchInfo::default()] //! pub fn reward_myself(origin) -> dispatch::DispatchResult { //! let reported = ensure_signed(origin)?; //! >::reward_by_ids(vec![(reported, 10)]); @@ -183,8 +184,8 @@ //! [`reward_by_indices`](./enum.Call.html#variant.reward_by_indices). //! //! [`Module`](./struct.Module.html) implements -//! [`pallet_authorship::EventHandler`](../pallet_authorship/trait.EventHandler.html) to add reward points -//! to block producer and block producer of referenced uncles. +//! [`pallet_authorship::EventHandler`](../pallet_authorship/trait.EventHandler.html) to add reward +//! points to block producer and block producer of referenced uncles. //! //! The validator and its nominator split their reward as following: //! @@ -243,37 +244,55 @@ //! ## Related Modules //! //! - [Balances](../pallet_balances/index.html): Used to manage values at stake. -//! - [Session](../pallet_session/index.html): Used to manage sessions. Also, a list of new validators -//! is stored in the Session module's `Validators` at the end of each era. +//! - [Session](../pallet_session/index.html): Used to manage sessions. Also, a list of new +//! validators is stored in the Session module's `Validators` at the end of each era. -#![recursion_limit="128"] +#![recursion_limit = "128"] #![cfg_attr(not(feature = "std"), no_std)] #[cfg(test)] mod mock; #[cfg(test)] mod tests; -mod slashing; +#[cfg(feature = "testing-utils")] +pub mod testing_utils; +#[cfg(any(feature = "runtime-benchmarks", test))] +pub mod benchmarking; +pub mod slashing; +pub mod offchain_election; pub mod inflation; -use sp_std::{prelude::*, result, collections::btree_map::BTreeMap}; +use sp_std::{ + result, + prelude::*, + collections::btree_map::BTreeMap, + convert::{TryInto, From}, + mem::size_of, +}; use codec::{HasCompact, Encode, Decode}; use frame_support::{ - decl_module, decl_event, decl_storage, ensure, decl_error, weights::SimpleDispatchInfo, - dispatch::DispatchResult, storage::IterableStorageMap, traits::{ + decl_module, decl_event, decl_storage, ensure, decl_error, debug, + weights::{SimpleDispatchInfo, Weight}, + storage::IterableStorageMap, + dispatch::{IsSubType, DispatchResult}, + traits::{ Currency, LockIdentifier, LockableCurrency, WithdrawReasons, OnUnbalanced, Imbalance, Get, - Time + UnixTime, EstimateNextNewSession, } }; -use pallet_session::historical::SessionManager; +use pallet_session::historical; use sp_runtime::{ - Perbill, PerThing, RuntimeDebug, + Perbill, PerU16, PerThing, RuntimeDebug, curve::PiecewiseLinear, traits::{ - Convert, Zero, StaticLookup, CheckedSub, Saturating, SaturatedConversion, - AtLeast32Bit, EnsureOrigin, - } + Convert, Zero, StaticLookup, CheckedSub, Saturating, SaturatedConversion, AtLeast32Bit, + EnsureOrigin, SignedExtension, + }, + transaction_validity::{ + TransactionValidityError, TransactionValidity, ValidTransaction, InvalidTransaction, + TransactionSource, + }, }; use sp_staking::{ SessionIndex, @@ -281,15 +300,33 @@ use sp_staking::{ }; #[cfg(feature = "std")] use sp_runtime::{Serialize, Deserialize}; -use frame_system::{self as system, ensure_signed, ensure_root}; - -use sp_phragmen::ExtendedBalance; -use frame_support::traits::MigrateAccount; +use frame_system::{ + self as system, ensure_signed, ensure_root, ensure_none, + offchain::SubmitUnsignedTransaction, +}; +use sp_phragmen::{ + ExtendedBalance, Assignment, PhragmenScore, PhragmenResult, build_support_map, evaluate_support, + elect, generate_compact_solution_type, is_score_better, VotingLimit, SupportMap, +}; const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4; -const MAX_NOMINATIONS: usize = 16; -const MAX_UNLOCKING_CHUNKS: usize = 32; const STAKING_ID: LockIdentifier = *b"staking "; +pub const MAX_UNLOCKING_CHUNKS: usize = 32; +pub const MAX_NOMINATIONS: usize = ::LIMIT; + +/// Data type used to index nominators in the compact type +pub type NominatorIndex = u32; + +/// Data type used to index validators in the compact type. +pub type ValidatorIndex = u16; + +// Ensure the size of both ValidatorIndex and NominatorIndex. They both need to be well below usize. +static_assertions::const_assert!(size_of::() <= size_of::()); +static_assertions::const_assert!(size_of::() <= size_of::()); + +/// Maximum number of stakers that can be stored in a snapshot. +pub(crate) const MAX_VALIDATORS: usize = ValidatorIndex::max_value() as usize; +pub(crate) const MAX_NOMINATORS: usize = NominatorIndex::max_value() as usize; /// Counter for the number of eras that have passed. pub type EraIndex = u32; @@ -297,18 +334,40 @@ pub type EraIndex = u32; /// Counter for the number of "reward" points earned by a given validator. pub type RewardPoint = u32; +// Note: Maximum nomination limit is set here -- 16. +generate_compact_solution_type!(pub GenericCompactAssignments, 16); + /// Information regarding the active era (era in used in session). #[derive(Encode, Decode, RuntimeDebug)] -pub struct ActiveEraInfo { +pub struct ActiveEraInfo { /// Index of era. index: EraIndex, - /// Moment of start + /// Moment of start expresed as millisecond from `$UNIX_EPOCH`. /// /// Start can be none if start hasn't been set for the era yet, /// Start is set on the first on_finalize of the era to guarantee usage of `Time`. - start: Option, + start: Option, } +/// Accuracy used for on-chain phragmen. +pub type ChainAccuracy = Perbill; + +/// Accuracy used for off-chain phragmen. This better be small. +pub type OffchainAccuracy = PerU16; + +/// The balance type of this module. +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// The compact type for election solutions. +pub type CompactAssignments = + GenericCompactAssignments; + +type PositiveImbalanceOf = + <::Currency as Currency<::AccountId>>::PositiveImbalance; +type NegativeImbalanceOf = + <::Currency as Currency<::AccountId>>::NegativeImbalance; + /// 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. @@ -557,13 +616,61 @@ pub struct UnappliedSlash { payout: Balance, } -pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; -type PositiveImbalanceOf = - <::Currency as Currency<::AccountId>>::PositiveImbalance; -type NegativeImbalanceOf = - <::Currency as Currency<::AccountId>>::NegativeImbalance; -type MomentOf = <::Time as Time>::Moment; +/// Indicate how an election round was computed. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug)] +pub enum ElectionCompute { + /// Result was forcefully computed on chain at the end of the session. + OnChain, + /// Result was submitted and accepted to the chain via a signed transaction. + Signed, + /// Result was submitted and accepted to the chain via an unsigned transaction (by an + /// authority). + Unsigned, +} + +/// The result of an election round. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] +pub struct ElectionResult { + /// Flat list of validators who have been elected. + elected_stashes: Vec, + /// Flat list of new exposures, to be updated in the [`Exposure`] storage. + exposures: Vec<(AccountId, Exposure)>, + /// Type of the result. This is kept on chain only to track and report the best score's + /// submission type. An optimisation could remove this. + compute: ElectionCompute, +} + +/// The status of the upcoming (offchain) election. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] +pub enum ElectionStatus { + /// Nothing has and will happen for now. submission window is not open. + Closed, + /// The submission window has been open since the contained block number. + Open(BlockNumber), +} + +impl ElectionStatus { + fn is_open_at(&self, n: BlockNumber) -> bool { + *self == Self::Open(n) + } + + fn is_closed(&self) -> bool { + match self { + Self::Closed => true, + _ => false + } + } + + fn is_open(&self) -> bool { + !self.is_closed() + } +} + +impl Default for ElectionStatus { + fn default() -> Self { + Self::Closed + } +} /// Means for interacting with a specialized version of the `session` trait. /// @@ -589,7 +696,8 @@ impl SessionInterface<::AccountId> for T whe >, T::SessionHandler: pallet_session::SessionHandler<::AccountId>, T::SessionManager: pallet_session::SessionManager<::AccountId>, - T::ValidatorIdOf: Convert<::AccountId, Option<::AccountId>> + T::ValidatorIdOf: + Convert<::AccountId, Option<::AccountId>>, { fn disable_validator(validator: &::AccountId) -> Result { >::disable(validator) @@ -612,7 +720,7 @@ pub trait Trait: frame_system::Trait { /// /// It is guaranteed to start being called from the first `on_finalize`. Thus value at genesis /// is not used. - type Time: Time; + type UnixTime: UnixTime; /// Convert a balance into a number used for election calculation. /// This must fit into a `u64` but is allowed to be sensibly lossy. @@ -653,6 +761,20 @@ pub trait Trait: frame_system::Trait { /// The NPoS reward curve to use. type RewardCurve: Get<&'static PiecewiseLinear<'static>>; + /// Something that can estimate the next session change, accurately or as a best effort guess. + type NextNewSession: EstimateNextNewSession; + + /// How many blocks ahead of the era, within the last do we try to run the phragmen offchain? + /// Setting this to zero will disable the offchain compute and only on-chain seq-phragmen will + /// be used. + type ElectionLookahead: Get; + + /// The overarching call type. + type Call: From> + IsSubType, Self> + Clone; + + /// A transaction submitter. + type SubmitTransaction: SubmitUnsignedTransaction::Call>; + /// The maximum number of nominator rewarded for each validator. /// /// For each validator only the `$MaxNominatorRewardedPerValidator` biggest stakers can claim @@ -685,11 +807,12 @@ impl Default for Forcing { enum Releases { V1_0_0Ancient, V2_0_0, + V3_0_0, } impl Default for Releases { fn default() -> Self { - Releases::V2_0_0 + Releases::V3_0_0 } } @@ -745,7 +868,7 @@ decl_storage! { /// /// The active era is the era currently rewarded. /// Validator set of this era must be equal to `SessionInterface::validators`. - pub ActiveEra get(fn active_era): Option>>; + pub ActiveEra get(fn active_era): Option; /// The session index at which the era start for the last `HISTORY_DEPTH` eras pub ErasStartSessionIndex get(fn eras_start_session_index): @@ -802,7 +925,7 @@ decl_storage! { pub ErasTotalStake get(fn eras_total_stake): map hasher(twox_64_concat) EraIndex => BalanceOf; - /// True if the next session change will be a new era regardless of index. + /// Mode of era forcing. pub ForceEra get(fn force_era) config(): Forcing; /// The percentage of the slash that is distributed to reporters. @@ -847,10 +970,34 @@ decl_storage! { /// The earliest era for which we have a pending, unapplied slash. EarliestUnappliedSlash: Option; + /// Snapshot of validators at the beginning of the current election window. This should only + /// have a value when [`EraElectionStatus`] == `ElectionStatus::Open(_)`. + pub SnapshotValidators get(fn snapshot_validators): Option>; + + /// Snapshot of nominators at the beginning of the current election window. This should only + /// have a value when [`EraElectionStatus`] == `ElectionStatus::Open(_)`. + pub SnapshotNominators get(fn snapshot_nominators): Option>; + + /// The next validator set. At the end of an era, if this is available (potentially from the + /// result of an offchain worker), it is immediately used. Otherwise, the on-chain election + /// is executed. + pub QueuedElected get(fn queued_elected): Option>>; + + /// The score of the current [`QueuedElected`]. + pub QueuedScore get(fn queued_score): Option; + + /// Flag to control the execution of the offchain election. When `Open(_)`, we accept + /// solutions to be submitted. + pub EraElectionStatus get(fn era_election_status): ElectionStatus; + + /// True if the current planned session is final. + pub IsCurrentSessionFinal get(fn is_current_session_final): bool = false; + + /// True if network has been upgraded to this version. /// Storage version of the pallet. /// - /// This is set to v2.0.0 for new networks. - StorageVersion build(|_: &GenesisConfig| Releases::V2_0_0): Releases; + /// This is set to v3.0.0 for new networks. + StorageVersion build(|_: &GenesisConfig| Releases::V3_0_0): Releases; } add_extra_genesis { config(stakers): @@ -895,6 +1042,8 @@ decl_event!( /// An old slashing report from a prior era was discarded because it could /// not be processed. OldSlashingReportDiscarded(SessionIndex), + /// A new set of stakers was elected with the given computation method. + StakingElection(ElectionCompute), /// An account has bonded this amount. /// /// NOTE: This event is only emitted when funds are bonded via a dispatchable. Notably, @@ -939,6 +1088,33 @@ decl_error! { InvalidNumberOfNominations, /// Items are not sorted and unique. NotSortedAndUnique, + /// The submitted result is received out of the open window. + PhragmenEarlySubmission, + /// The submitted result is not as good as the one stored on chain. + PhragmenWeakSubmission, + /// The snapshot data of the current window is missing. + SnapshotUnavailable, + /// Incorrect number of winners were presented. + PhragmenBogusWinnerCount, + /// One of the submitted winners is not an active candidate on chain (index is out of range + /// in snapshot). + PhragmenBogusWinner, + /// Error while building the assignment type from the compact. This can happen if an index + /// is invalid, or if the weights _overflow_. + PhragmenBogusCompact, + /// One of the submitted nominators is not an active nominator on chain. + PhragmenBogusNominator, + /// One of the submitted nominators has an edge to which they have not voted on chain. + PhragmenBogusNomination, + /// One of the submitted nominators has an edge which is submitted before the last non-zero + /// slash of the target. + PhragmenSlashedNomination, + /// A self vote must only be originated from a validator to ONLY themselves. + PhragmenBogusSelfVote, + /// The submitted result has unknown edges that are not among the presented winners. + PhragmenBogusEdge, + /// The claimed score does not match with the one computed from the data. + PhragmenBogusScore, } } @@ -954,20 +1130,102 @@ decl_module! { fn deposit_event() = default; - fn on_runtime_upgrade() { - migrate::(); + /// sets `ElectionStatus` to `Open(now)` where `now` is the block number at which the + /// election window has opened, if we are at the last session and less blocks than + /// `T::ElectionLookahead` is remaining until the next new session schedule. The offchain + /// worker, if applicable, will execute at the end of the current block, and solutions may + /// be submitted. + fn on_initialize(now: T::BlockNumber) -> Weight { + if + // if we don't have any ongoing offchain compute. + Self::era_election_status().is_closed() && + Self::is_current_session_final() + { + if let Some(next_session_change) = T::NextNewSession::estimate_next_new_session(now){ + if let Some(remaining) = next_session_change.checked_sub(&now) { + if remaining <= T::ElectionLookahead::get() && !remaining.is_zero() { + // create snapshot. + if Self::create_stakers_snapshot() { + // Set the flag to make sure we don't waste any compute here in the same era + // after we have triggered the offline compute. + >::put( + ElectionStatus::::Open(now) + ); + debug::native::info!( + target: "staking", + "Election window is Open({:?}). Snapshot created", + now, + ); + } else { + debug::native::warn!( + target: "staking", + "Failed to create snapshot at {:?}. Election window will remain closed.", + now, + ); + } + + } + } + } else { + debug::native::warn!( + target: "staking", + "estimate_next_new_session() failed to execute. Election status cannot be changed.", + ); + } + } + + // weight + 50_000 + } + + /// Check if the current block number is the one at which the election window has been set + /// to open. If so, it runs the offchain worker code. + fn offchain_worker(now: T::BlockNumber) { + use offchain_election::{set_check_offchain_execution_status, compute_offchain_election}; + + if Self::era_election_status().is_open_at(now) { + let offchain_status = set_check_offchain_execution_status::(now); + if let Err(why) = offchain_status { + debug::native::warn!( + target: "staking", + "skipping offchain worker in open election window due to [{}]", + why, + ); + } else { + if let Err(e) = compute_offchain_election::() { + debug::native::warn!( + target: "staking", + "Error in phragmen offchain worker: {:?}", + e, + ); + } else { + debug::native::debug!( + target: "staking", + "Executed offchain worker thread without errors. Transaction submitted to the pool.", + ); + } + } + } } fn on_finalize() { // Set the start of the first era. if let Some(mut active_era) = Self::active_era() { if active_era.start.is_none() { - active_era.start = Some(T::Time::now()); - >::put(active_era); + let now_as_millis_u64 = T::UnixTime::now().as_millis().saturated_into::(); + active_era.start = Some(now_as_millis_u64); + ActiveEra::put(active_era); } } } + fn on_runtime_upgrade() -> Weight { + // For Kusama the type hasn't actually changed as Moment was u64 and was the number of + // millisecond since unix epoch. + StorageVersion::put(Releases::V3_0_0); + 0 + } + /// Take the origin account as a stash and lock up `value` of its balance. `controller` will /// be the account that controls it. /// @@ -982,14 +1240,14 @@ decl_module! { /// - O(1). /// - Three extra DB entries. /// - /// NOTE: Two of the storage writes (`Self::bonded`, `Self::payee`) are _never_ cleaned unless - /// the `origin` falls below _existential deposit_ and gets removed as dust. + /// NOTE: Two of the storage writes (`Self::bonded`, `Self::payee`) are _never_ cleaned + /// unless the `origin` falls below _existential deposit_ and gets removed as dust. /// # #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn bond(origin, controller: ::Source, #[compact] value: BalanceOf, - payee: RewardDestination + payee: RewardDestination, ) { let stash = ensure_signed(origin)?; @@ -1192,7 +1450,7 @@ decl_module! { /// /// # /// - The transaction's complexity is proportional to the size of `targets`, - /// which is capped at `MAX_NOMINATIONS`. + /// which is capped at CompactAssignments::LIMIT. /// - Both the reads and writes follow a similar pattern. /// # #[weight = SimpleDispatchInfo::FixedNormal(750_000)] @@ -1202,7 +1460,7 @@ decl_module! { let stash = &ledger.stash; ensure!(!targets.is_empty(), Error::::EmptyTargets); let targets = targets.into_iter() - .take(MAX_NOMINATIONS) + .take(::LIMIT) .map(|t| T::Lookup::lookup(t)) .collect::, _>>()?; @@ -1281,6 +1539,8 @@ decl_module! { } } + // ----- Root calls. + /// The ideal number of validators. #[weight = SimpleDispatchInfo::FixedNormal(5_000)] fn set_validator_count(origin, #[compact] new: u32) { @@ -1288,8 +1548,6 @@ decl_module! { ValidatorCount::put(new); } - // ----- Root calls. - /// Force there to be no new eras indefinitely. /// /// # @@ -1357,7 +1615,7 @@ decl_module! { .or_else(ensure_root)?; ensure!(!slash_indices.is_empty(), Error::::EmptyTargets); - ensure!(Self::is_sorted_and_unique(&slash_indices), Error::::NotSortedAndUnique); + ensure!(is_sorted_and_unique(&slash_indices), Error::::NotSortedAndUnique); let mut unapplied = ::UnappliedSlashes::get(&era); let last_item = slash_indices[slash_indices.len() - 1]; @@ -1470,52 +1728,185 @@ decl_module! { /// This can be called from any origin. /// /// - `stash`: The stash account to reap. Its balance must be zero. + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn reap_stash(_origin, stash: T::AccountId) { ensure!(T::Currency::total_balance(&stash).is_zero(), Error::::FundedTarget); Self::kill_stash(&stash)?; T::Currency::remove_lock(STAKING_ID, &stash); } - } -} -impl MigrateAccount for Module { - fn migrate_account(a: &T::AccountId) { - if let Some(controller) = Bonded::::migrate_key_from_blake(a) { - Ledger::::migrate_key_from_blake(controller); - Payee::::migrate_key_from_blake(a); - Validators::::migrate_key_from_blake(a); - Nominators::::migrate_key_from_blake(a); - SlashingSpans::::migrate_key_from_blake(a); + /// Submit a phragmen result to the chain. If the solution: + /// + /// 1. is valid. + /// 2. has a better score than a potentially existing solution on chain. + /// + /// then, it will be _put_ on chain. + /// + /// A solution consists of two pieces of data: + /// + /// 1. `winners`: a flat vector of all the winners of the round. + /// 2. `assignments`: the compact version of an assignment vector that encodes the edge + /// weights. + /// + /// Both of which may be computed using [`phragmen`], or any other algorithm. + /// + /// Additionally, the submitter must provide: + /// + /// - The `score` that they claim their solution has. + /// + /// Both validators and nominators will be represented by indices in the solution. The + /// indices should respect the corresponding types ([`ValidatorIndex`] and + /// [`NominatorIndex`]). Moreover, they should be valid when used to index into + /// [`SnapshotValidators`] and [`SnapshotNominators`]. Any invalid index will cause the + /// solution to be rejected. These two storage items are set during the election window and + /// may be used to determine the indices. + /// + /// A solution is valid if: + /// + /// 0. It is submitted when [`EraElectionStatus`] is `Open`. + /// 1. Its claimed score is equal to the score computed on-chain. + /// 2. Presents the correct number of winners. + /// 3. All indexes must be value according to the snapshot vectors. All edge values must + /// also be correct and should not overflow the granularity of the ratio type (i.e. 256 + /// or billion). + /// 4. For each edge, all targets are actually nominated by the voter. + /// 5. Has correct self-votes. + /// + /// A solutions score is consisted of 3 parameters: + /// + /// 1. `min { support.total }` for each support of a winner. This value should be maximized. + /// 2. `sum { support.total }` for each support of a winner. This value should be minimized. + /// 3. `sum { support.total^2 }` for each support of a winner. This value should be + /// minimized (to ensure less variance) + /// + /// # + /// E: number of edges. m: size of winner committee. n: number of nominators. d: edge degree + /// (16 for now) v: number of on-chain validator candidates. + /// + /// NOTE: given a solution which is reduced, we can enable a new check the ensure `|E| < n + + /// m`. We don't do this _yet_, but our offchain worker code executes it nonetheless. + /// + /// major steps (all done in `check_and_replace_solution`): + /// + /// - Storage: O(1) read `ElectionStatus`. + /// - Storage: O(1) read `PhragmenScore`. + /// - Storage: O(1) read `ValidatorCount`. + /// - Storage: O(1) length read from `SnapshotValidators`. + /// + /// - Storage: O(v) reads of `AccountId` to fetch `snapshot_validators`. + /// - Memory: O(m) iterations to map winner index to validator id. + /// - Storage: O(n) reads `AccountId` to fetch `snapshot_nominators`. + /// - Memory: O(n + m) reads to map index to `AccountId` for un-compact. + /// + /// - Storage: O(e) accountid reads from `Nomination` to read correct nominations. + /// - Storage: O(e) calls into `slashable_balance_of_extended` to convert ratio to staked. + /// + /// - Memory: build_support_map. O(e). + /// - Memory: evaluate_support: O(E). + /// + /// - Storage: O(e) writes to `QueuedElected`. + /// - Storage: O(1) write to `QueuedScore` + /// + /// The weight of this call is 1/10th of the blocks total weight. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(100_000_000)] + pub fn submit_election_solution( + origin, + winners: Vec, + compact_assignments: CompactAssignments, + score: PhragmenScore, + era: EraIndex, + ) { + let _who = ensure_signed(origin)?; + Self::check_and_replace_solution( + winners, + compact_assignments, + ElectionCompute::Signed, + score, + era, + )? } - } -} -fn migrate() { - if let Some(current_era) = CurrentEra::get() { - let history_depth = HistoryDepth::get(); - for era in current_era.saturating_sub(history_depth)..=current_era { - ErasStartSessionIndex::migrate_key_from_blake(era); - ErasValidatorReward::::migrate_key_from_blake(era); - ErasRewardPoints::::migrate_key_from_blake(era); - ErasTotalStake::::migrate_key_from_blake(era); + /// Unsigned version of `submit_election_solution`. + /// + /// Note that this must pass the [`ValidateUnsigned`] check which only allows transactions + /// from the local node to be included. In other words, only the block author can include a + /// transaction in the block. + #[weight = SimpleDispatchInfo::FixedNormal(100_000_000)] + pub fn submit_election_solution_unsigned( + origin, + winners: Vec, + compact_assignments: CompactAssignments, + score: PhragmenScore, + era: EraIndex, + ) { + ensure_none(origin)?; + Self::check_and_replace_solution( + winners, + compact_assignments, + ElectionCompute::Unsigned, + score, + era, + )? + // TODO: instead of returning an error, panic. This makes the entire produced block + // invalid. + // This ensures that block authors will not ever try and submit a solution which is not + // an improvement, since they will lose their authoring points/rewards. } } } impl Module { - // PUBLIC IMMUTABLES - /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default() } - /// Check that list is sorted and has no duplicates. - fn is_sorted_and_unique(list: &Vec) -> bool { - list.windows(2).all(|w| w[0] < w[1]) + /// internal impl of [`slashable_balance_of`] that returns [`ExtendedBalance`]. + fn slashable_balance_of_extended(stash: &T::AccountId) -> ExtendedBalance { + , u64>>::convert( + Self::slashable_balance_of(stash) + ) as ExtendedBalance } - // MUTABLES (DANGEROUS) + /// Dump the list of validators and nominators into vectors and keep them on-chain. + /// + /// This data is used to efficiently evaluate election results. returns `true` if the operation + /// is successful. + fn create_stakers_snapshot() -> bool { + let validators = >::iter().map(|(v, _)| v).collect::>(); + let mut nominators = >::iter().map(|(n, _)| n).collect::>(); + + let num_validators = validators.len(); + let num_nominators = nominators.len(); + if + num_validators > MAX_VALIDATORS || + num_nominators.saturating_add(num_validators) > MAX_NOMINATORS + { + debug::native::warn!( + target: "staking", + "Snapshot size too big [{} <> {}][{} <> {}].", + num_validators, + MAX_VALIDATORS, + num_nominators, + MAX_NOMINATORS, + ); + false + } else { + // all validators nominate themselves; + nominators.extend(validators.clone()); + + >::put(validators); + >::put(nominators); + true + } + } + + /// Clears both snapshots of stakers. + fn kill_stakers_snapshot() { + >::kill(); + >::kill(); + } fn do_payout_nominator(who: T::AccountId, era: EraIndex, validators: Vec<(T::AccountId, u32)>) -> DispatchResult @@ -1684,9 +2075,17 @@ impl Module { Forcing::ForceNew => ForceEra::kill(), Forcing::ForceAlways => (), Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (), - _ => return None, + _ => { + // not forcing, not a new era either. If final, set the flag. + if era_length + 1 >= T::SessionsPerEra::get() { + IsCurrentSessionFinal::put(true); + } + return None + }, } + // new era. + IsCurrentSessionFinal::put(false); Self::new_era(session_index) } else { // Set initial era @@ -1694,6 +2093,188 @@ impl Module { } } + /// Basic and cheap checks that we perform in validate unsigned, and in the execution. + pub fn pre_dispatch_checks(score: PhragmenScore, era: EraIndex) -> Result<(), Error> { + // discard solutions that are not in-time + // check window open + ensure!( + Self::era_election_status().is_open(), + Error::::PhragmenEarlySubmission, + ); + + // check current era. + if let Some(current_era) = Self::active_era().map(|e| e.index) { + ensure!( + current_era == era, + Error::::PhragmenEarlySubmission, + ) + } + + // assume the given score is valid. Is it better than what we have on-chain, if we have any? + if let Some(queued_score) = Self::queued_score() { + ensure!( + is_score_better(queued_score, score), + Error::::PhragmenWeakSubmission, + ) + } + + Ok(()) + } + + /// Checks a given solution and if correct and improved, writes it on chain as the queued result + /// of the next round. This may be called by both a signed and an unsigned transaction. + pub fn check_and_replace_solution( + winners: Vec, + compact_assignments: CompactAssignments, + compute: ElectionCompute, + claimed_score: PhragmenScore, + era: EraIndex, + ) -> Result<(), Error> { + // Do the basic checks. era, claimed score and window open. + Self::pre_dispatch_checks(claimed_score, era)?; + + // Check that the number of presented winners is sane. Most often we have more candidates + // that we need. Then it should be Self::validator_count(). Else it should be all the + // candidates. + let snapshot_length = >::decode_len() + .map_err(|_| Error::::SnapshotUnavailable)?; + let desired_winners = Self::validator_count().min(snapshot_length as u32); + ensure!(winners.len() as u32 == desired_winners, Error::::PhragmenBogusWinnerCount); + + // decode snapshot validators. + let snapshot_validators = Self::snapshot_validators() + .ok_or(Error::::SnapshotUnavailable)?; + + // check if all winners were legit; this is rather cheap. Replace with accountId. + let winners = winners.into_iter().map(|widx| { + // NOTE: at the moment, since staking is explicitly blocking any offence until election + // is closed, we don't check here if the account id at `snapshot_validators[widx]` is + // actually a validator. If this ever changes, this loop needs to also check this. + snapshot_validators.get(widx as usize).cloned().ok_or(Error::::PhragmenBogusWinner) + }).collect::, Error>>()?; + + // decode the rest of the snapshot. + let snapshot_nominators = >::snapshot_nominators() + .ok_or(Error::::SnapshotUnavailable)?; + + // helpers + let nominator_at = |i: NominatorIndex| -> Option { + snapshot_nominators.get(i as usize).cloned() + }; + let validator_at = |i: ValidatorIndex| -> Option { + snapshot_validators.get(i as usize).cloned() + }; + + // un-compact. + let assignments = compact_assignments.into_assignment( + nominator_at, + validator_at, + ).map_err(|e| { + // log the error since it is not propagated into the runtime error. + debug::native::warn!( + target: "staking", + "un-compacting solution failed due to {:?}", + e, + ); + Error::::PhragmenBogusCompact + })?; + + // check all nominators actually including the claimed vote. Also check correct self votes. + // Note that we assume all validators and nominators in `assignments` are properly bonded, + // because they are coming from the snapshot via a given index. + for Assignment { who, distribution } in assignments.iter() { + let is_validator = >::contains_key(&who); + let maybe_nomination = Self::nominators(&who); + + if !(maybe_nomination.is_some() ^ is_validator) { + // all of the indices must map to either a validator or a nominator. If this is ever + // not the case, then the locking system of staking is most likely faulty, or we + // have bigger problems. + debug::native::error!( + target: "staking", + "detected an error in the staking locking and snapshot." + ); + // abort. + return Err(Error::::PhragmenBogusNominator); + } + + if !is_validator { + // a normal vote + let nomination = maybe_nomination.expect( + "exactly one of `maybe_validator` and `maybe_nomination.is_some` is true. \ + is_validator is false; maybe_nomination is some; qed" + ); + + // NOTE: we don't really have to check here if the sum of all edges are the + // nominator correct. Un-compacting assures this by definition. + + for (t, _) in distribution { + // each target in the provided distribution must be actually nominated by the + // nominator after the last non-zero slash. + if nomination.targets.iter().find(|&tt| tt == t).is_none() { + return Err(Error::::PhragmenBogusNomination); + } + + if ::SlashingSpans::get(&t).map_or( + false, + |spans| nomination.submitted_in < spans.last_nonzero_slash(), + ) { + return Err(Error::::PhragmenSlashedNomination); + } + } + } else { + // a self vote + ensure!(distribution.len() == 1, Error::::PhragmenBogusSelfVote); + ensure!(distribution[0].0 == *who, Error::::PhragmenBogusSelfVote); + // defensive only. A compact assignment of length one does NOT encode the weight and + // it is always created to be 100%. + ensure!( + distribution[0].1 == OffchainAccuracy::one(), + Error::::PhragmenBogusSelfVote, + ); + } + } + + // convert into staked assignments. + let staked_assignments = sp_phragmen::assignment_ratio_to_staked( + assignments, + Self::slashable_balance_of_extended, + ); + + // build the support map thereof in order to evaluate. + // OPTIMIZATION: loop to create the staked assignments but it would bloat the code. Okay for + // now as it does not add to the complexity order. + let (supports, num_error) = build_support_map::( + &winners, + &staked_assignments, + ); + // This technically checks that all targets in all nominators were among the winners. + ensure!(num_error == 0, Error::::PhragmenBogusEdge); + + // Check if the score is the same as the claimed one. + let submitted_score = evaluate_support(&supports); + ensure!(submitted_score == claimed_score, Error::::PhragmenBogusScore); + + // At last, alles Ok. Exposures and store the result. + let exposures = Self::collect_exposure(supports); + debug::native::info!( + target: "staking", + "A better solution (with compute {:?}) has been validated and stored on chain.", + compute, + ); + + // write new results. + >::put(ElectionResult { + elected_stashes: winners, + compute, + exposures, + }); + QueuedScore::put(submitted_score); + + Ok(()) + + } + /// Start a session potentially starting an era. fn start_session(start_session: SessionIndex) { let next_active_era = Self::active_era().map(|e| e.index + 1).unwrap_or(0); @@ -1728,7 +2309,7 @@ impl Module { /// * reset `active_era.start`, /// * update `BondedEras` and apply slashes. fn start_era(start_session: SessionIndex) { - let active_era = >::mutate(|active_era| { + let active_era = ActiveEra::mutate(|active_era| { let new_index = active_era.as_ref().map(|info| info.index + 1).unwrap_or(0); *active_era = Some(ActiveEraInfo { index: new_index, @@ -1766,12 +2347,12 @@ impl Module { } /// Compute payout for era. - fn end_era(active_era: ActiveEraInfo>, _session_index: SessionIndex) { + fn end_era(active_era: ActiveEraInfo, _session_index: SessionIndex) { // Note: active_era_start can be None if end era is called during genesis config. if let Some(active_era_start) = active_era.start { - let now = T::Time::now(); + let now_as_millis_u64 = T::UnixTime::now().as_millis().saturated_into::(); - let era_duration = now - active_era_start; + let era_duration = now_as_millis_u64 - active_era_start; let (total_payout, _max_payout) = inflation::compute_total_payout( &T::RewardCurve::get(), Self::eras_total_stake(&active_era.index), @@ -1800,55 +2381,160 @@ impl Module { } // Set staking information for new era. - let maybe_new_validators = Self::select_validators(current_era); + let maybe_new_validators = Self::select_and_update_validators(current_era); maybe_new_validators } - /// Clear all era information for given era. - fn clear_era_information(era_index: EraIndex) { - >::remove_prefix(era_index); - >::remove_prefix(era_index); - >::remove_prefix(era_index); - >::remove(era_index); - >::remove(era_index); - >::remove(era_index); - ErasStartSessionIndex::remove(era_index); - } + /// Select the new validator set at the end of the era. + /// + /// Runs [`try_do_phragmen`] and updates the following storage items: + /// - [`EraElectionStatus`]: with `None`. + /// - [`ErasStakers`]: with the new staker set. + /// - [`ErasStakersClipped`]. + /// - [`ErasValidatorPrefs`]. + /// - [`ErasTotalStake`]: with the new total stake. + /// - [`SnapshotValidators`] and [`SnapshotNominators`] are both removed. + /// + /// Internally, [`QueuedElected`], snapshots and [`QueuedScore`] are also consumed. + /// + /// If the election has been successful, It passes the new set upwards. + /// + /// This should only be called at the end of an era. + fn select_and_update_validators(current_era: EraIndex) -> Option> { + if let Some(ElectionResult::> { + elected_stashes, + exposures, + compute, + }) = Self::try_do_phragmen() { + // We have chosen the new validator set. Submission is no longer allowed. + >::put(ElectionStatus::Closed); + + // kill the snapshots. + Self::kill_stakers_snapshot(); + + // Populate Stakers and write slot stake. + let mut total_stake: BalanceOf = Zero::zero(); + exposures.into_iter().for_each(|(stash, exposure)| { + total_stake = total_stake.saturating_add(exposure.total); + >::insert(current_era, &stash, &exposure); - /// 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); + let mut exposure_clipped = exposure; + let clipped_max_len = T::MaxNominatorRewardedPerValidator::get() as usize; + if exposure_clipped.others.len() > clipped_max_len { + exposure_clipped.others.sort_unstable_by(|a, b| a.value.cmp(&b.value).reverse()); + exposure_clipped.others.truncate(clipped_max_len); } + >::insert(¤t_era, &stash, exposure_clipped); + }); + + // Insert current era staking information + >::insert(¤t_era, total_stake); + + // collect the pref of all winners + for stash in &elected_stashes { + let pref = Self::validators(stash); + >::insert(¤t_era, stash, pref); } - *earliest = (*earliest).max(keep_from) - }) + // emit event + Self::deposit_event(RawEvent::StakingElection(compute)); + + debug::native::info!( + target: "staking", + "new validator set of size {:?} has been elected via {:?} for era {:?}", + elected_stashes.len(), + compute, + current_era, + ); + + Some(elected_stashes) + } else { + None + } + } + + /// Select a new validator set from the assembled stakers and their role preferences. It tries + /// first to peek into [`QueuedElected`]. Otherwise, it runs a new phragmen. + /// + /// If [`QueuedElected`] and [`QueuedScore`] exists, they are both removed. No further storage + /// is updated. + fn try_do_phragmen() -> Option>> { + // a phragmen result from either a stored submission or locally executed one. + let next_result = >::take().or_else(|| + Self::do_phragmen_with_post_processing::(ElectionCompute::OnChain) + ); + + // either way, kill this. We remove it here to make sure it always has the exact same + // lifetime as `QueuedElected`. + QueuedScore::kill(); + + next_result } - /// Select a new validator set from the assembled stakers and their role preferences, and store - /// staking information for the new current era. + /// Execute phragmen and return the new results. The edge weights are processed into support + /// values. /// - /// Fill the storages `ErasStakers`, `ErasStakersClipped`, `ErasValidatorPrefs` and - /// `ErasTotalStake` for current era. + /// This is basically a wrapper around [`do_phragmen`] which translates `PhragmenResult` into + /// `ElectionResult`. /// - /// Returns a set of newly selected _stash_ IDs. + /// No storage item is updated. + fn do_phragmen_with_post_processing(compute: ElectionCompute) + -> Option>> + where + Accuracy: sp_std::ops::Mul, + ExtendedBalance: From<::Inner>, + { + if let Some(phragmen_result) = Self::do_phragmen::() { + let elected_stashes = phragmen_result.winners.iter() + .map(|(s, _)| s.clone()) + .collect::>(); + let assignments = phragmen_result.assignments; + + let staked_assignments = sp_phragmen::assignment_ratio_to_staked( + assignments, + Self::slashable_balance_of_extended, + ); + + let (supports, _) = build_support_map::( + &elected_stashes, + &staked_assignments, + ); + + // collect exposures + let exposures = Self::collect_exposure(supports); + + // In order to keep the property required by `on_session_ending` that we must return the + // new validator set even if it's the same as the old, as long as any underlying + // economic conditions have changed, we don't attempt to do any optimization where we + // compare against the prior set. + Some(ElectionResult::> { + elected_stashes, + exposures, + compute, + }) + } else { + // There were not enough candidates for even our minimal level of functionality. This is + // bad. We should probably disable all functionality except for block production and let + // the chain keep producing blocks until we can decide on a sufficiently substantial + // set. TODO: #2494 + None + } + } + + /// Execute phragmen and return the new results. No post-processing is applied and the raw edge + /// weights are returned. + /// + /// Self votes are added and nominations before the most recent slashing span are reaped. /// - /// Assumes storage is coherent with the declaration. - fn select_validators(current_era: EraIndex) -> Option> { - let mut all_nominators: Vec<(T::AccountId, Vec)> = Vec::new(); - let mut all_validators_and_prefs = BTreeMap::new(); + /// No storage item is updated. + fn do_phragmen() -> Option> { + let mut all_nominators: Vec<(T::AccountId, BalanceOf, Vec)> = Vec::new(); let mut all_validators = Vec::new(); - for (validator, preference) in >::iter() { - let self_vote = (validator.clone(), vec![validator.clone()]); + for (validator, _) in >::iter() { + // append self vote + let self_vote = (validator.clone(), Self::slashable_balance_of(&validator), vec![validator.clone()]); all_nominators.push(self_vote); - all_validators_and_prefs.insert(validator.clone(), preference); all_validators.push(validator); } @@ -1856,7 +2542,7 @@ impl Module { let Nominations { submitted_in, mut targets, suppressed: _ } = nominations; // Filter out nomination targets which were nominated before the most recent - // non-zero slash. + // slashing span. targets.retain(|stash| { ::SlashingSpans::get(&stash).map_or( true, @@ -1866,95 +2552,49 @@ impl Module { (nominator, targets) }); - all_nominators.extend(nominator_votes); + all_nominators.extend(nominator_votes.map(|(n, ns)| { + let s = Self::slashable_balance_of(&n); + (n, s, ns) + })); - let maybe_phragmen_result = sp_phragmen::elect::<_, _, _, T::CurrencyToVote, Perbill>( + elect::<_, _, T::CurrencyToVote, Accuracy>( Self::validator_count() as usize, Self::minimum_validator_count().max(1) as usize, all_validators, all_nominators, - Self::slashable_balance_of, - ); - - if let Some(phragmen_result) = maybe_phragmen_result { - let elected_stashes = phragmen_result.winners.into_iter() - .map(|(s, _)| s) - .collect::>(); - let assignments = phragmen_result.assignments; - - let to_balance = |e: ExtendedBalance| - >>::convert(e); - - let supports = sp_phragmen::build_support_map::<_, _, _, T::CurrencyToVote, Perbill>( - &elected_stashes, - &assignments, - Self::slashable_balance_of, - ); - - // Populate stakers information and figure out the total stake. - let mut total_staked = BalanceOf::::zero(); - for (c, s) in supports.into_iter() { - // build `struct exposure` from `support` - let mut others = Vec::new(); - let mut own: BalanceOf = Zero::zero(); - let mut total: BalanceOf = Zero::zero(); - s.voters - .into_iter() - .map(|(who, value)| (who, to_balance(value))) - .for_each(|(who, value)| { - if who == c { - own = own.saturating_add(value); - } else { - others.push(IndividualExposure { who, value }); - } - total = total.saturating_add(value); - }); - - total_staked = total_staked.saturating_add(total); - - let exposure = Exposure { - own, - others, - // This might reasonably saturate and we cannot do much about it. The sum of - // someone's stake might exceed the balance type if they have the maximum amount - // of balance and receive some support. This is super unlikely to happen, yet - // we simulate it in some tests. - total, - }; - >::insert(¤t_era, &c, &exposure); + ) + } - let mut exposure_clipped = exposure; - let clipped_max_len = T::MaxNominatorRewardedPerValidator::get() as usize; - if exposure_clipped.others.len() > clipped_max_len { - exposure_clipped.others.sort_unstable_by(|a, b| a.value.cmp(&b.value).reverse()); - exposure_clipped.others.truncate(clipped_max_len); - } - >::insert(¤t_era, &c, exposure_clipped); - } + /// Consume a set of [`Supports`] from [`sp_phragmen`] and collect them into a [`Exposure`] + fn collect_exposure(supports: SupportMap) -> Vec<(T::AccountId, Exposure>)> { + let to_balance = |e: ExtendedBalance| + >>::convert(e); + + supports.into_iter().map(|(validator, support)| { + // build `struct exposure` from `support` + let mut others = Vec::new(); + let mut own: BalanceOf = Zero::zero(); + let mut total: BalanceOf = Zero::zero(); + support.voters + .into_iter() + .map(|(nominator, weight)| (nominator, to_balance(weight))) + .for_each(|(nominator, stake)| { + if nominator == validator { + own = own.saturating_add(stake); + } else { + others.push(IndividualExposure { who: nominator, value: stake }); + } + total = total.saturating_add(stake); + }); - // Insert current era staking informations - >::insert(¤t_era, total_staked); - let default_pref = ValidatorPrefs::default(); - for stash in &elected_stashes { - let pref = all_validators_and_prefs.get(stash) - .unwrap_or(&default_pref); // Must never happen, but better to be safe. - >::insert(¤t_era, stash, pref); - } + let exposure = Exposure { + own, + others, + total, + }; - // In order to keep the property required by `n_session_ending` - // that we must return the new validator set even if it's the same as the old, - // as long as any underlying economic conditions have changed, we don't attempt - // to do any optimization where we compare against the prior set. - Some(elected_stashes) - } else { - // There were not enough candidates for even our minimal level of functionality. - // This is bad. - // We should probably disable all functionality except for block production - // and let the chain keep producing blocks until we can decide on a sufficiently - // substantial set. - // TODO: #2494 - None - } + (validator, exposure) + }).collect::)>>() } /// Remove all associated data of a stash account from the staking system. @@ -1979,6 +2619,33 @@ impl Module { Ok(()) } + /// Clear all era information for given era. + fn clear_era_information(era_index: EraIndex) { + >::remove_prefix(era_index); + >::remove_prefix(era_index); + >::remove_prefix(era_index); + >::remove(era_index); + >::remove(era_index); + >::remove(era_index); + ErasStartSessionIndex::remove(era_index); + } + + /// 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) + }) + } + /// Add reward points to validators using their stash account ID. /// /// Validators are keyed by stash account ID and must be in the current elected set. @@ -2030,9 +2697,7 @@ impl pallet_session::SessionManager for Module { } } -/// This implementation has the same constrains as the implementation of -/// `pallet_session::SessionManager`. -impl SessionManager>> for Module { +impl historical::SessionManager>> for Module { fn new_session(new_index: SessionIndex) -> Option>)>> { @@ -2118,15 +2783,20 @@ impl OnOffenceHandler>], slash_fraction: &[Perbill], slash_session: SessionIndex, - ) { + ) -> Result<(), ()> { + if !Self::can_report() { + return Err(()) + } + let reward_proportion = SlashRewardFraction::get(); let active_era = { let active_era = Self::active_era(); if active_era.is_none() { - return + // this offence need not be re-submitted. + return Ok(()) } - active_era.unwrap().index + active_era.expect("value checked not to be `None`; qed").index }; let active_era_start_session_index = Self::eras_start_session_index(active_era) .unwrap_or_else(|| { @@ -2145,7 +2815,7 @@ impl OnOffenceHandler return, // before bonding period. defensive - should be filtered out. + None => return Ok(()), // before bonding period. defensive - should be filtered out. Some(&(ref slash_era, _)) => *slash_era, } }; @@ -2159,8 +2829,7 @@ impl OnOffenceHandler OnOffenceHandler bool { + Self::era_election_status().is_closed() } } @@ -2220,3 +2895,160 @@ impl ReportOffence } } } + +/// Disallows any transactions that change the election result to be submitted after the election +/// window is open. +#[derive(Encode, Decode, Clone, Eq, PartialEq)] +pub struct LockStakingStatus(sp_std::marker::PhantomData); + +impl sp_std::fmt::Debug for LockStakingStatus { + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "LockStakingStatus") + } +} + +impl LockStakingStatus { + /// Create new `LockStakingStatus`. + pub fn new() -> Self { + Self(sp_std::marker::PhantomData) + } +} + +impl Default for LockStakingStatus { + fn default() -> Self { + Self::new() + } +} + +impl SignedExtension for LockStakingStatus { + const IDENTIFIER: &'static str = "LockStakingStatus"; + type AccountId = T::AccountId; + type Call = ::Call; + type AdditionalSigned = (); + type DispatchInfo = frame_support::weights::DispatchInfo; + type Pre = (); + + fn additional_signed(&self) -> Result<(), TransactionValidityError> { Ok(()) } + + fn validate( + &self, + _who: &Self::AccountId, + call: &Self::Call, + _info: Self::DispatchInfo, + _len: usize, + ) -> TransactionValidity { + if let Some(inner_call) = call.is_sub_type() { + if let ElectionStatus::Open(_) = >::era_election_status() { + match inner_call { + Call::::set_payee(..) | + Call::::set_controller(..) | + Call::::set_validator_count(..) | + Call::::force_no_eras(..) | + Call::::force_new_era(..) | + Call::::set_invulnerables(..) | + Call::::force_unstake(..) | + Call::::force_new_era_always(..) | + Call::::cancel_deferred_slash(..) | + Call::::set_history_depth(..) | + Call::::reap_stash(..) | + Call::::submit_election_solution(..) | + Call::::submit_election_solution_unsigned(..) => { + // These calls are allowed. Nothing. + } + _ => { + return Err(InvalidTransaction::Stale.into()); + } + } + } + } + + Ok(Default::default()) + } +} + +impl From> for InvalidTransaction { + fn from(e: Error) -> Self { + match e { + >::PhragmenEarlySubmission => InvalidTransaction::Future, + _ => InvalidTransaction::Custom(e.as_u8()), + } + } +} + +#[allow(deprecated)] +impl frame_support::unsigned::ValidateUnsigned for Module { + type Call = Call; + fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::submit_election_solution_unsigned( + _, + _, + score, + era, + ) = call { + use offchain_election::DEFAULT_LONGEVITY; + + // discard solution not coming from the local OCW. + match source { + TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ } + _ => { + debug::native::debug!( + target: "staking", + "rejecting unsigned transaction because it is not local/in-block." + ); + return InvalidTransaction::Call.into(); + } + } + + if let Err(e) = Self::pre_dispatch_checks(*score, *era) { + debug::native::debug!( + target: "staking", + "validate unsigned failed due to {:?}.", + e, + ); + let invalid: InvalidTransaction = e.into(); + return invalid.into(); + } + + debug::native::debug!( + target: "staking", + "Validated an unsigned transaction from the local node for era {}.", + era, + ); + + Ok(ValidTransaction { + // The higher the score[0], the better a solution is. + priority: score[0].saturated_into(), + // no requires. + requires: vec![], + // Defensive only. A single solution can exist in the pool per era. Each validator + // will run OCW at most once per era, hence there should never exist more than one + // transaction anyhow. + provides: vec![("StakingOffchain", era).encode()], + // Note: this can be more accurate in the future. We do something like + // `era_end_block - current_block` but that is not needed now as we eagerly run + // offchain workers now and the above should be same as `T::ElectionLookahead` + // without the need to query more storage in the validation phase. If we randomize + // offchain worker, then we might re-consider this. + longevity: TryInto::::try_into(T::ElectionLookahead::get()).unwrap_or(DEFAULT_LONGEVITY), + // We don't propagate this. This can never the validated at a remote node. + propagate: false, + }) + } else { + InvalidTransaction::Call.into() + } + } + + fn pre_dispatch(_: &Self::Call) -> Result<(), TransactionValidityError> { + // IMPORTANT NOTE: By default, a sane `pre-dispatch` should always do the same checks as + // `validate_unsigned` and overriding this should be done with care. this module has only + // one unsigned entry point, in which we call into `>::pre_dispatch_checks()` + // which is all the important checks that we do in `validate_unsigned`. Hence, we can safely + // override this to save some time. + Ok(()) + } +} + +/// Check that list is sorted and has no duplicates. +fn is_sorted_and_unique(list: &[u32]) -> bool { + list.windows(2).all(|w| w[0] < w[1]) +} diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index a09bd8151f118ca3ba6e27063765fe46aafde312..b90064ae47f4dd1e7b2332dcb6d388fe27804527 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -17,56 +17,76 @@ //! Test utilities use std::{collections::{HashSet, HashMap}, cell::RefCell}; -use sp_runtime::{Perbill, KeyTypeId}; +use sp_runtime::Perbill; use sp_runtime::curve::PiecewiseLinear; -use sp_runtime::traits::{IdentityLookup, Convert, OpaqueKeys, OnInitialize, OnFinalize, SaturatedConversion}; -use sp_runtime::testing::{Header, UintAuthorityId}; +use sp_runtime::traits::{IdentityLookup, Convert, SaturatedConversion, Zero}; +use sp_runtime::testing::{Header, UintAuthorityId, TestXt}; use sp_staking::{SessionIndex, offence::{OffenceDetails, OnOffenceHandler}}; -use sp_core::{H256, crypto::key_types}; -use sp_io; +use sp_core::H256; use frame_support::{ - assert_ok, impl_outer_origin, parameter_types, StorageValue, StorageMap, - StorageDoubleMap, IterableStorageMap, traits::{Currency, Get, FindAuthor}, weights::Weight, + assert_ok, impl_outer_origin, parameter_types, impl_outer_dispatch, impl_outer_event, + StorageValue, StorageMap, StorageDoubleMap, IterableStorageMap, + traits::{Currency, Get, FindAuthor, OnFinalize, OnInitialize}, + weights::Weight, +}; +use frame_system::offchain::TransactionSubmitter; +use sp_io; +use sp_phragmen::{ + build_support_map, evaluate_support, reduce, ExtendedBalance, StakedAssignment, PhragmenScore, }; use crate::{ EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination, - Nominators, inflation, SessionInterface, Exposure, ErasStakers, ErasRewardPoints + Nominators, inflation, SessionInterface, Exposure, ErasStakers, ErasRewardPoints, + CompactAssignments, ValidatorIndex, NominatorIndex, Validators, OffchainAccuracy, }; +const INIT_TIMESTAMP: u64 = 30_000; + /// The AccountId alias in this test module. -pub type AccountId = u64; -pub type BlockNumber = u64; -pub type Balance = u64; +pub(crate) type AccountId = u64; +pub(crate) type AccountIndex = u64; +pub(crate) type BlockNumber = u64; +pub(crate) type Balance = u64; /// Simple structure that exposes how u64 currency can be represented as... u64. pub struct CurrencyToVoteHandler; impl Convert for CurrencyToVoteHandler { - fn convert(x: u64) -> u64 { x } + fn convert(x: u64) -> u64 { + x + } } impl Convert for CurrencyToVoteHandler { - fn convert(x: u128) -> u64 { x.saturated_into() } + fn convert(x: u128) -> u64 { + x.saturated_into() + } } thread_local! { static SESSION: RefCell<(Vec, HashSet)> = RefCell::new(Default::default()); + static SESSION_PER_ERA: RefCell = RefCell::new(3); static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); static SLASH_DEFER_DURATION: RefCell = RefCell::new(0); + static ELECTION_LOOKAHEAD: RefCell = RefCell::new(0); + static PERIOD: RefCell = RefCell::new(1); } -pub struct TestSessionHandler; -impl pallet_session::SessionHandler for TestSessionHandler { - const KEY_TYPE_IDS: &'static [KeyTypeId] = &[key_types::DUMMY]; +/// Another session handler struct to test on_disabled. +pub struct OtherSessionHandler; +impl pallet_session::OneSessionHandler for OtherSessionHandler { + type Key = UintAuthorityId; - fn on_genesis_session(_validators: &[(AccountId, Ks)]) {} + fn on_genesis_session<'a, I: 'a>(_: I) + where I: Iterator, AccountId: 'a {} - fn on_new_session( - _changed: bool, - validators: &[(AccountId, Ks)], - _queued_validators: &[(AccountId, Ks)], - ) { - SESSION.with(|x| - *x.borrow_mut() = (validators.iter().map(|x| x.0.clone()).collect(), HashSet::new()) - ); + fn on_new_session<'a, I: 'a>(_: bool, validators: 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) { @@ -78,6 +98,10 @@ impl pallet_session::SessionHandler for TestSessionHandler { } } +impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { + type Public = UintAuthorityId; +} + pub fn is_disabled(controller: AccountId) -> bool { let stash = Staking::ledger(&controller).unwrap().stash; SESSION.with(|d| d.borrow().1.contains(&stash)) @@ -90,6 +114,32 @@ impl Get for ExistentialDeposit { } } +pub struct SessionsPerEra; +impl Get for SessionsPerEra { + fn get() -> SessionIndex { + SESSION_PER_ERA.with(|v| *v.borrow()) + } +} +impl Get for SessionsPerEra { + fn get() -> BlockNumber { + SESSION_PER_ERA.with(|v| *v.borrow() as BlockNumber) + } +} + +pub struct ElectionLookahead; +impl Get for ElectionLookahead { + fn get() -> BlockNumber { + ELECTION_LOOKAHEAD.with(|v| *v.borrow()) + } +} + +pub struct Period; +impl Get for Period { + fn get() -> BlockNumber { + PERIOD.with(|v| *v.borrow()) + } +} + pub struct SlashDeferDuration; impl Get for SlashDeferDuration { fn get() -> EraIndex { @@ -97,23 +147,47 @@ impl Get for SlashDeferDuration { } } -impl_outer_origin!{ +impl_outer_origin! { pub enum Origin for Test where system = frame_system {} } +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + staking::Staking, + } +} + +mod staking { + // Re-export needed for `impl_outer_event!`. + pub use super::super::*; +} +use frame_system as system; +use pallet_balances as balances; +use pallet_session as session; + +impl_outer_event! { + pub enum MetaEvent for Test { + system, + balances, + session, + staking, + } +} + /// Author of block is always 11 pub struct Author11; impl FindAuthor for Author11 { fn find_author<'a, I>(_digests: I) -> Option - where I: 'a + IntoIterator + where I: 'a + IntoIterator, { Some(11) } } // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, Eq, PartialEq, Debug)] pub struct Test; + parameter_types! { pub const BlockHashCount: u64 = 250; pub const MaximumBlockWeight: Weight = 1024; @@ -122,15 +196,15 @@ parameter_types! { } impl frame_system::Trait for Test { type Origin = Origin; - type Index = u64; + type Index = AccountIndex; type BlockNumber = BlockNumber; - type Call = (); + type Call = Call; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; - type Event = (); + type Event = MetaEvent; type BlockHashCount = BlockHashCount; type MaximumBlockWeight = MaximumBlockWeight; type AvailableBlockRatio = AvailableBlockRatio; @@ -138,31 +212,36 @@ impl frame_system::Trait for Test { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } impl pallet_balances::Trait for Test { type Balance = Balance; + type Event = MetaEvent; type DustRemoval = (); - type Event = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; } parameter_types! { - pub const Period: BlockNumber = 1; pub const Offset: BlockNumber = 0; pub const UncleGenerations: u64 = 0; pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(25); } +sp_runtime::impl_opaque_keys! { + pub struct SessionKeys { + pub other: OtherSessionHandler, + } +} impl pallet_session::Trait for Test { - type Event = (); + type SessionManager = pallet_session::historical::NoteHistoricalRoot; + type Keys = SessionKeys; + type ShouldEndSession = pallet_session::PeriodicSessions; + type SessionHandler = (OtherSessionHandler,); + type Event = MetaEvent; type ValidatorId = AccountId; type ValidatorIdOf = crate::StashOf; - type ShouldEndSession = pallet_session::PeriodicSessions; - type SessionManager = pallet_session::historical::NoteHistoricalRoot; - type SessionHandler = TestSessionHandler; - type Keys = UintAuthorityId; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = pallet_session::PeriodicSessions; } impl pallet_session::historical::Trait for Test { @@ -194,17 +273,17 @@ pallet_staking_reward_curve::build! { ); } parameter_types! { - pub const SessionsPerEra: SessionIndex = 3; pub const BondingDuration: EraIndex = 3; pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS; pub const MaxNominatorRewardedPerValidator: u32 = 64; } + impl Trait for Test { - type Currency = pallet_balances::Module; - type Time = pallet_timestamp::Module; + type Currency = Balances; + type UnixTime = Timestamp; type CurrencyToVote = CurrencyToVoteHandler; type RewardRemainder = (); - type Event = (); + type Event = MetaEvent; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; @@ -213,11 +292,21 @@ impl Trait for Test { type BondingDuration = BondingDuration; type SessionInterface = Self; type RewardCurve = RewardCurve; + type NextNewSession = Session; + type ElectionLookahead = ElectionLookahead; + type Call = Call; + type SubmitTransaction = SubmitTransaction; type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; } +pub type Extrinsic = TestXt; +type SubmitTransaction = TransactionSubmitter<(), Test, Extrinsic>; + pub struct ExtBuilder { - existential_deposit: u64, + session_length: BlockNumber, + election_lookahead: BlockNumber, + session_per_era: SessionIndex, + existential_deposit: Balance, validator_pool: bool, nominate: bool, validator_count: u32, @@ -225,12 +314,16 @@ pub struct ExtBuilder { slash_defer_duration: EraIndex, fair: bool, num_validators: Option, - invulnerables: Vec, + invulnerables: Vec, + has_stakers: bool, } impl Default for ExtBuilder { fn default() -> Self { Self { + session_length: 1, + election_lookahead: 0, + session_per_era: 3, existential_deposit: 1, validator_pool: false, nominate: true, @@ -240,6 +333,7 @@ impl Default for ExtBuilder { fair: true, num_validators: None, invulnerables: vec![], + has_stakers: true, } } } @@ -281,13 +375,40 @@ impl ExtBuilder { self.invulnerables = invulnerables; self } - pub fn set_associated_consts(&self) { + pub fn session_per_era(mut self, length: SessionIndex) -> Self { + self.session_per_era = length; + self + } + pub fn election_lookahead(mut self, look: BlockNumber) -> Self { + self.election_lookahead = look; + self + } + pub fn session_length(mut self, length: BlockNumber) -> Self { + self.session_length = length; + self + } + pub fn has_stakers(mut self, has: bool) -> Self { + self.has_stakers = has; + self + } + pub fn offchain_phragmen_ext(self) -> Self { + self.session_per_era(4) + .session_length(5) + .election_lookahead(3) + } + pub fn set_associated_constants(&self) { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = self.slash_defer_duration); + SESSION_PER_ERA.with(|v| *v.borrow_mut() = self.session_per_era); + ELECTION_LOOKAHEAD.with(|v| *v.borrow_mut() = self.election_lookahead); + PERIOD.with(|v| *v.borrow_mut() = self.session_length); } pub fn build(self) -> sp_io::TestExternalities { - self.set_associated_consts(); - let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let _ = env_logger::try_init(); + self.set_associated_constants(); + let mut storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); let balance_factor = if self.existential_deposit > 1 { 256 } else { @@ -299,7 +420,7 @@ impl ExtBuilder { .map(|x| ((x + 1) * 10 + 1) as u64) .collect::>(); - let _ = pallet_balances::GenesisConfig::{ + let _ = pallet_balances::GenesisConfig:: { balances: vec![ (1, 10 * balance_factor), (2, 20 * balance_factor), @@ -320,16 +441,17 @@ impl ExtBuilder { ], }.assimilate_storage(&mut storage); - let stake_21 = if self.fair { 1000 } else { 2000 }; - let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 }; - let status_41 = if self.validator_pool { - StakerStatus::::Validator - } else { - StakerStatus::::Idle - }; - let nominated = if self.nominate { vec![11, 21] } else { vec![] }; - let _ = GenesisConfig::{ - stakers: vec![ + let mut stakers = vec![]; + if self.has_stakers { + let stake_21 = if self.fair { 1000 } else { 2000 }; + let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 }; + let status_41 = if self.validator_pool { + StakerStatus::::Validator + } else { + StakerStatus::::Idle + }; + let nominated = if self.nominate { vec![11, 21] } else { vec![] }; + stakers = vec![ // (stash, controller, staked_amount, status) (11, 10, balance_factor * 1000, StakerStatus::::Validator), (21, 20, stake_21, StakerStatus::::Validator), @@ -337,25 +459,40 @@ impl ExtBuilder { (41, 40, balance_factor * 1000, status_41), // nominator (101, 100, balance_factor * 500, StakerStatus::::Nominator(nominated)) - ], + ]; + } + let _ = GenesisConfig::{ + stakers: stakers, 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); + } + .assimilate_storage(&mut storage); let _ = pallet_session::GenesisConfig:: { - keys: validators.iter().map(|x| (*x, *x, UintAuthorityId(*x))).collect(), + keys: validators.iter().map(|x| ( + *x, + *x, + SessionKeys { other: UintAuthorityId(*x) } + )).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()) - ); + SESSION.with(|x| *x.borrow_mut() = (validators.clone(), HashSet::new())); }); + + // We consider all test to start after timestamp is initialized + // This must be ensured by having `timestamp::on_initialize` called before + // `staking::on_initialize` + ext.execute_with(|| { + System::set_block_number(1); + Timestamp::set_timestamp(INIT_TIMESTAMP); + }); + ext } } @@ -366,6 +503,10 @@ pub type Session = pallet_session::Module; pub type Timestamp = pallet_timestamp::Module; pub type Staking = Module; +pub fn active_era() -> EraIndex { + Staking::active_era().unwrap().index +} + pub fn check_exposure_all(era: EraIndex) { ErasStakers::::iter_prefix(era).for_each(check_exposure) } @@ -378,8 +519,9 @@ pub fn check_nominator_all(era: EraIndex) { /// Check for each selected validator: expo.total = Sum(expo.other) + expo.own pub fn check_exposure(expo: Exposure) { assert_eq!( - expo.total as u128, expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::(), - "wrong total exposure {:?}", expo, + expo.total as u128, + expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::(), + "wrong total exposure", ); } @@ -388,17 +530,18 @@ pub fn check_exposure(expo: Exposure) { pub fn check_nominator_exposure(era: EraIndex, stash: AccountId) { assert_is_stash(stash); let mut sum = 0; - ErasStakers::::iter_prefix(era) - .for_each(|exposure| { - exposure.others.iter() - .filter(|i| i.who == stash) - .for_each(|i| sum += i.value) - }); + Session::validators() + .iter() + .map(|v| Staking::eras_stakers(era, v)) + .for_each(|e| e.others.iter().filter(|i| i.who == stash).for_each(|i| sum += i.value)); let nominator_stake = Staking::slashable_balance_of(&stash); // a nominator cannot over-spend. assert!( nominator_stake >= sum, - "failed: Nominator({}) stake({}) >= sum divided({})", stash, nominator_stake, sum, + "failed: Nominator({}) stake({}) >= sum divided({})", + stash, + nominator_stake, + sum, ); } @@ -414,20 +557,41 @@ pub fn assert_ledger_consistent(stash: AccountId) { assert_eq!(real_total, ledger.total); } -pub fn bond_validator(acc: u64, val: u64) { - // a = controller - // a + 1 = stash - let _ = Balances::make_free_balance_be(&(acc + 1), val); - assert_ok!(Staking::bond(Origin::signed(acc + 1), acc, val, RewardDestination::Controller)); - assert_ok!(Staking::validate(Origin::signed(acc), ValidatorPrefs::default())); -} - -pub fn bond_nominator(acc: u64, val: u64, target: Vec) { - // a = controller - // a + 1 = stash - let _ = Balances::make_free_balance_be(&(acc + 1), val); - assert_ok!(Staking::bond(Origin::signed(acc + 1), acc, val, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(acc), target)); +pub fn bond_validator(stash: u64, ctrl: u64, val: u64) { + let _ = Balances::make_free_balance_be(&stash, val); + assert_ok!(Staking::bond( + Origin::signed(stash), + ctrl, + val, + RewardDestination::Controller, + )); + assert_ok!(Staking::validate( + Origin::signed(ctrl), + ValidatorPrefs::default() + )); +} + +pub fn bond_nominator(stash: u64, ctrl: u64, val: u64, target: Vec) { + let _ = Balances::make_free_balance_be(&stash, val); + assert_ok!(Staking::bond( + Origin::signed(stash), + ctrl, + val, + RewardDestination::Controller, + )); + assert_ok!(Staking::nominate(Origin::signed(ctrl), target)); +} + +pub 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); + Staking::on_initialize(b); + if b != n { + Staking::on_finalize(System::block_number()); + } + } } pub fn advance_session() { @@ -436,19 +600,21 @@ pub fn advance_session() { } pub fn start_session(session_index: SessionIndex) { + assert_eq!(>::get(), 1, "start_session can only be used with session length 1."); for i in Session::current_index()..session_index { Staking::on_finalize(System::block_number()); System::set_block_number((i + 1).into()); - Timestamp::set_timestamp(System::block_number() * 1000); + Timestamp::set_timestamp(System::block_number() * 1000 + INIT_TIMESTAMP); Session::on_initialize(System::block_number()); + Staking::on_initialize(System::block_number()); } assert_eq!(Session::current_index(), session_index); } pub fn start_era(era_index: EraIndex) { - start_session((era_index * 3).into()); - assert_eq!(Staking::active_era().unwrap().index, era_index); + start_session((era_index * >::get()).into()); + assert_eq!(Staking::current_era().unwrap(), era_index); } pub fn current_total_payout_for_duration(duration: u64) -> u64 { @@ -461,33 +627,45 @@ pub fn current_total_payout_for_duration(duration: u64) -> u64 { } pub fn reward_all_elected() { - let rewards = ::SessionInterface::validators().into_iter() + let rewards = ::SessionInterface::validators() + .into_iter() .map(|v| (v, 1)); >::reward_by_ids(rewards) } pub fn validator_controllers() -> Vec { - Session::validators().into_iter().map(|s| Staking::bonded(&s).expect("no controller for validator")).collect() + Session::validators() + .into_iter() + .map(|s| Staking::bonded(&s).expect("no controller for validator")) + .collect() } pub fn on_offence_in_era( - offenders: &[OffenceDetails>], + offenders: &[OffenceDetails< + AccountId, + pallet_session::historical::IdentificationTuple, + >], slash_fraction: &[Perbill], era: EraIndex, ) { let bonded_eras = crate::BondedEras::get(); for &(bonded_era, start_session) in bonded_eras.iter() { if bonded_era == era { - Staking::on_offence(offenders, slash_fraction, start_session); - return + let _ = Staking::on_offence(offenders, slash_fraction, start_session).unwrap(); + return; } else if bonded_era > era { - break + break; } } if Staking::active_era().unwrap().index == era { - Staking::on_offence(offenders, slash_fraction, Staking::eras_start_session_index(era).unwrap()); + let _ = + Staking::on_offence( + offenders, + slash_fraction, + Staking::eras_start_session_index(era).unwrap() + ).unwrap(); } else { panic!("cannot slash in era {}", era); } @@ -501,6 +679,193 @@ pub fn on_offence_now( on_offence_in_era(offenders, slash_fraction, now) } +// winners will be chosen by simply their unweighted total backing stake. Nominator stake is +// distributed evenly. +pub fn horrible_phragmen_with_post_processing( + do_reduce: bool, +) -> (CompactAssignments, Vec, PhragmenScore) { + use std::collections::BTreeMap; + + let mut backing_stake_of: BTreeMap = BTreeMap::new(); + + // self stake + >::iter().for_each(|(who, _p)| { + *backing_stake_of.entry(who).or_insert(Zero::zero()) += Staking::slashable_balance_of(&who) + }); + + // add nominator stuff + >::iter().for_each(|(who, nomination)| { + nomination.targets.iter().for_each(|v| { + *backing_stake_of.entry(*v).or_insert(Zero::zero()) += + Staking::slashable_balance_of(&who) + }) + }); + + // elect winners + let mut sorted: Vec = backing_stake_of.keys().cloned().collect(); + sorted.sort_by_key(|x| backing_stake_of.get(x).unwrap()); + let winners: Vec = sorted + .iter() + .cloned() + .take(Staking::validator_count() as usize) + .collect(); + + // create assignments + let mut staked_assignment: Vec> = Vec::new(); + >::iter().for_each(|(who, nomination)| { + let mut dist: Vec<(AccountId, ExtendedBalance)> = Vec::new(); + nomination.targets.iter().for_each(|v| { + if winners.iter().find(|w| *w == v).is_some() { + dist.push((*v, ExtendedBalance::zero())); + } + }); + + if dist.len() == 0 { + return; + } + + // assign real stakes. just split the stake. + let stake = Staking::slashable_balance_of(&who) as ExtendedBalance; + let mut sum: ExtendedBalance = Zero::zero(); + let dist_len = dist.len(); + { + dist.iter_mut().for_each(|(_, w)| { + let partial = stake / (dist_len as ExtendedBalance); + *w = partial; + sum += partial; + }); + } + + // assign the leftover to last. + { + let leftover = stake - sum; + let last = dist.last_mut().unwrap(); + last.1 += leftover; + } + + staked_assignment.push(StakedAssignment { + who, + distribution: dist, + }); + }); + + // Ensure that this result is worse than seq-phragmen. Otherwise, it should not have been used + // for testing. + let score = { + let (_, _, better_score) = prepare_submission_with(true, |_| {}); + + let support = build_support_map::(&winners, &staked_assignment).0; + let score = evaluate_support(&support); + + assert!(sp_phragmen::is_score_better(score, better_score)); + + score + }; + + if do_reduce { + reduce(&mut staked_assignment); + } + + let snapshot_validators = Staking::snapshot_validators().unwrap(); + let snapshot_nominators = Staking::snapshot_nominators().unwrap(); + let nominator_index = |a: &AccountId| -> Option { + snapshot_nominators.iter().position(|x| x == a).map(|i| i as NominatorIndex) + }; + let validator_index = |a: &AccountId| -> Option { + snapshot_validators.iter().position(|x| x == a).map(|i| i as ValidatorIndex) + }; + + // convert back to ratio assignment. This takes less space. + let assignments_reduced = + sp_phragmen::assignment_staked_to_ratio::(staked_assignment); + + let compact = + CompactAssignments::from_assignment(assignments_reduced, nominator_index, validator_index) + .unwrap(); + + // winner ids to index + let winners = winners.into_iter().map(|w| validator_index(&w).unwrap()).collect::>(); + + (compact, winners, score) +} + +// Note: this should always logically reproduce [`offchain_election::prepare_submission`], yet we +// cannot do it since we want to have `tweak` injected into the process. +pub fn prepare_submission_with( + do_reduce: bool, + tweak: impl FnOnce(&mut Vec>), +) -> (CompactAssignments, Vec, PhragmenScore) { + // run phragmen on the default stuff. + let sp_phragmen::PhragmenResult { + winners, + assignments, + } = Staking::do_phragmen::().unwrap(); + let winners = winners.into_iter().map(|(w, _)| w).collect::>(); + + let stake_of = |who: &AccountId| -> ExtendedBalance { + >::convert( + Staking::slashable_balance_of(&who) + ) as ExtendedBalance + }; + let mut staked = sp_phragmen::assignment_ratio_to_staked(assignments, stake_of); + + // apply custom tweaks. awesome for testing. + tweak(&mut staked); + + if do_reduce { + reduce(&mut staked); + } + + // convert back to ratio assignment. This takes less space. + let snapshot_validators = Staking::snapshot_validators().expect("snapshot not created."); + let snapshot_nominators = Staking::snapshot_nominators().expect("snapshot not created."); + let nominator_index = |a: &AccountId| -> Option { + snapshot_nominators + .iter() + .position(|x| x == a) + .map_or_else( + || { println!("unable to find nominator index for {:?}", a); None }, + |i| Some(i as NominatorIndex), + ) + }; + let validator_index = |a: &AccountId| -> Option { + snapshot_validators + .iter() + .position(|x| x == a) + .map_or_else( + || { println!("unable to find validator index for {:?}", a); None }, + |i| Some(i as ValidatorIndex), + ) + }; + + let assignments_reduced = sp_phragmen::assignment_staked_to_ratio(staked); + + // re-compute score by converting, yet again, into staked type + let score = { + let staked = sp_phragmen::assignment_ratio_to_staked( + assignments_reduced.clone(), + Staking::slashable_balance_of_extended, + ); + + let (support_map, _) = build_support_map::( + winners.as_slice(), + staked.as_slice(), + ); + evaluate_support::(&support_map) + }; + + let compact = + CompactAssignments::from_assignment(assignments_reduced, nominator_index, validator_index) + .map_err(|e| { println!("error in compact: {:?}", e); e }) + .expect("Failed to create compact"); + + + // winner ids to index + let winners = winners.into_iter().map(|w| validator_index(&w).unwrap()).collect::>(); + + (compact, winners, score) +} + /// Make all validator and nominator request their payment pub fn make_all_reward_payment(era: EraIndex) { let validators_with_reward = ErasRewardPoints::::get(era).individual.keys() @@ -532,3 +897,23 @@ pub fn make_all_reward_payment(era: EraIndex) { assert_ok!(Staking::payout_validator(Origin::signed(validator_controller), era)); } } + +#[macro_export] +macro_rules! assert_session_era { + ($session:expr, $era:expr) => { + assert_eq!( + Session::current_index(), + $session, + "wrong session {} != {}", + Session::current_index(), + $session, + ); + assert_eq!( + Staking::active_era().unwrap().index, + $era, + "wrong active era {} != {}", + Staking::active_era().unwrap().index, + $era, + ); + }; +} diff --git a/frame/staking/src/offchain_election.rs b/frame/staking/src/offchain_election.rs new file mode 100644 index 0000000000000000000000000000000000000000..0d4cf49f103b9fd634ad178235159244f4f743b2 --- /dev/null +++ b/frame/staking/src/offchain_election.rs @@ -0,0 +1,219 @@ +// Copyright 2020 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 . + +//! Helpers for offchain worker election. + +use crate::{ + Call, CompactAssignments, Module, NominatorIndex, OffchainAccuracy, Trait, ValidatorIndex, +}; +use frame_system::offchain::SubmitUnsignedTransaction; +use sp_phragmen::{ + build_support_map, evaluate_support, reduce, Assignment, ExtendedBalance, PhragmenResult, + PhragmenScore, +}; +use sp_runtime::offchain::storage::StorageValueRef; +use sp_runtime::PerThing; +use sp_runtime::RuntimeDebug; +use sp_std::{convert::TryInto, prelude::*}; + +/// Error types related to the offchain election machinery. +#[derive(RuntimeDebug)] +pub enum OffchainElectionError { + /// Phragmen election returned None. This means less candidate that minimum number of needed + /// validators were present. The chain is in trouble and not much that we can do about it. + ElectionFailed, + /// Submission to the transaction pool failed. + PoolSubmissionFailed, + /// The snapshot data is not available. + SnapshotUnavailable, + /// Error from phragmen crate. This usually relates to compact operation. + PhragmenError(sp_phragmen::Error), + /// One of the computed winners is invalid. + InvalidWinner, +} + +impl From for OffchainElectionError { + fn from(e: sp_phragmen::Error) -> Self { + Self::PhragmenError(e) + } +} + +/// Storage key used to store the persistent offchain worker status. +pub(crate) const OFFCHAIN_HEAD_DB: &[u8] = b"parity/staking-election/"; +/// The repeat threshold of the offchain worker. This means we won't run the offchain worker twice +/// within a window of 5 blocks. +pub(crate) const OFFCHAIN_REPEAT: u32 = 5; +/// Default number of blocks for which the unsigned transaction should stay in the pool +pub(crate) const DEFAULT_LONGEVITY: u64 = 25; + +/// Checks if an execution of the offchain worker is permitted at the given block number, or not. +/// +/// This essentially makes sure that we don't run on previous blocks in case of a re-org, and we +/// don't run twice within a window of length [`OFFCHAIN_REPEAT`]. +/// +/// Returns `Ok(())` if offchain worker should happen, `Err(reason)` otherwise. +pub(crate) fn set_check_offchain_execution_status( + now: T::BlockNumber, +) -> Result<(), &'static str> { + let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB); + let threshold = T::BlockNumber::from(OFFCHAIN_REPEAT); + + let mutate_stat = + storage.mutate::<_, &'static str, _>(|maybe_head: Option>| { + match maybe_head { + Some(Some(head)) if now < head => Err("fork."), + Some(Some(head)) if now >= head && now <= head + threshold => { + Err("recently executed.") + } + Some(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(_)) => Ok(()), + // failed to write. + Ok(Err(_)) => Err("failed to write to offchain db."), + // fork etc. + Err(why) => Err(why), + } +} + +/// The internal logic of the offchain worker of this module. This runs the phragmen election, +/// compacts and reduces the solution, computes the score and submits it back to the chain as an +/// unsigned transaction, without any signature. +pub(crate) fn compute_offchain_election() -> Result<(), OffchainElectionError> { + // compute raw solution. Note that we use `OffchainAccuracy`. + let PhragmenResult { + winners, + assignments, + } = >::do_phragmen::() + .ok_or(OffchainElectionError::ElectionFailed)?; + + // process and prepare it for submission. + let (winners, compact, score) = prepare_submission::(assignments, winners, true)?; + + // defensive-only: active era can never be none except genesis. + let era = >::active_era().map(|e| e.index).unwrap_or_default(); + + // send it. + let call: ::Call = Call::submit_election_solution_unsigned( + winners, + compact, + score, + era, + ).into(); + + T::SubmitTransaction::submit_unsigned(call) + .map_err(|_| OffchainElectionError::PoolSubmissionFailed) +} + +/// Takes a phragmen result and spits out some data that can be submitted to the chain. +/// +/// This does a lot of stuff; read the inline comments. +pub fn prepare_submission( + assignments: Vec>, + winners: Vec<(T::AccountId, ExtendedBalance)>, + do_reduce: bool, +) -> Result<(Vec, CompactAssignments, PhragmenScore), OffchainElectionError> where + ExtendedBalance: From<::Inner>, +{ + // make sure that the snapshot is available. + let snapshot_validators = + >::snapshot_validators().ok_or(OffchainElectionError::SnapshotUnavailable)?; + let snapshot_nominators = + >::snapshot_nominators().ok_or(OffchainElectionError::SnapshotUnavailable)?; + + // all helper closures + let nominator_index = |a: &T::AccountId| -> Option { + snapshot_nominators + .iter() + .position(|x| x == a) + .and_then(|i| >::try_into(i).ok()) + }; + let validator_index = |a: &T::AccountId| -> Option { + snapshot_validators + .iter() + .position(|x| x == a) + .and_then(|i| >::try_into(i).ok()) + }; + + // Clean winners. + let winners = winners + .into_iter() + .map(|(w, _)| w) + .collect::>(); + + // convert into absolute value and to obtain the reduced version. + let mut staked = sp_phragmen::assignment_ratio_to_staked( + assignments, + >::slashable_balance_of_extended, + ); + + if do_reduce { + reduce(&mut staked); + } + + // Convert back to ratio assignment. This takes less space. + let low_accuracy_assignment = sp_phragmen::assignment_staked_to_ratio(staked); + + // convert back to staked to compute the score in the receiver's accuracy. This can be done + // nicer, for now we do it as such since this code is not time-critical. This ensure that the + // score _predicted_ here is the same as the one computed on chain and you will not get a + // `PhragmenBogusScore` error. This is totally NOT needed if we don't do reduce. This whole + // _accuracy glitch_ happens because reduce breaks that assumption of rounding and **scale**. + // The initial phragmen results are computed in `OffchainAccuracy` and the initial `staked` + // assignment set is also all multiples of this value. After reduce, this no longer holds. Hence + // converting to ratio thereafter is not trivially reversible. + let score = { + let staked = sp_phragmen::assignment_ratio_to_staked( + low_accuracy_assignment.clone(), + >::slashable_balance_of_extended, + ); + + let (support_map, _) = build_support_map::(&winners, &staked); + evaluate_support::(&support_map) + }; + + // compact encode the assignment. + let compact = CompactAssignments::from_assignment( + low_accuracy_assignment, + nominator_index, + validator_index, + ).map_err(|e| OffchainElectionError::from(e))?; + + // winners to index. Use a simple for loop for a more expressive early exit in case of error. + let mut winners_indexed: Vec = Vec::with_capacity(winners.len()); + for w in winners { + if let Some(idx) = snapshot_validators.iter().position(|v| *v == w) { + let compact_index: ValidatorIndex = idx + .try_into() + .map_err(|_| OffchainElectionError::InvalidWinner)?; + winners_indexed.push(compact_index); + } else { + return Err(OffchainElectionError::InvalidWinner); + } + } + + Ok((winners_indexed, compact, score)) +} diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 3c8f39501a25e8c8b1fe2306c4d8ceb596b5e457..26f0828989d733bfc720de288f33f0efcafd462d 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -16,11 +16,11 @@ //! A slashing implementation for NPoS systems. //! -//! For the purposes of the economic model, it is easiest to think of each validator -//! of a nominator which nominates only its own identity. +//! For the purposes of the economic model, it is easiest to think of each validator as a nominator +//! which nominates only its own identity. //! -//! The act of nomination signals intent to unify economic identity with the validator - to take part in the -//! rewards of a job well done, and to take part in the punishment of a job done badly. +//! The act of nomination signals intent to unify economic identity with the validator - to take +//! part in the rewards of a job well done, and to take part in the punishment of a job done badly. //! //! There are 3 main difficulties to account for with slashing in NPoS: //! - A nominator can nominate multiple validators and be slashed via any of them. @@ -52,7 +52,7 @@ use super::{ EraIndex, Trait, Module, Store, BalanceOf, Exposure, Perbill, SessionInterface, NegativeImbalanceOf, UnappliedSlash, }; -use sp_runtime::{traits::{Zero, Saturating}, PerThing}; +use sp_runtime::{traits::{Zero, Saturating}, RuntimeDebug}; use frame_support::{ StorageMap, StorageDoubleMap, traits::{Currency, OnUnbalanced, Imbalance}, @@ -65,7 +65,7 @@ use codec::{Encode, Decode}; const REWARD_F1: Perbill = Perbill::from_percent(50); /// The index of a slashing span - unique to each stash. -pub(crate) type SpanIndex = u32; +pub type SpanIndex = u32; // A range of start..end eras for a slashing span. #[derive(Encode, Decode)] @@ -83,7 +83,7 @@ impl SlashingSpan { } /// An encoding of all of a nominator's slashing spans. -#[derive(Encode, Decode)] +#[derive(Encode, Decode, RuntimeDebug)] pub struct SlashingSpans { // the index of the current slashing span of the nominator. different for // every stash, resets when the account hits free balance 0. @@ -143,7 +143,7 @@ impl SlashingSpans { } /// Yields the era index where the most recent non-zero slash occurred. - pub(crate) fn last_nonzero_slash(&self) -> EraIndex { + pub fn last_nonzero_slash(&self) -> EraIndex { self.last_nonzero_slash } @@ -566,7 +566,7 @@ pub(crate) fn clear_stash_metadata(stash: &T::AccountId) { // apply the slash to a stash account, deducting any missing funds from the reward // payout, saturating at 0. this is mildly unfair but also an edge-case that // can only occur when overlapping locked funds have been slashed. -fn do_slash( +pub fn do_slash( stash: &T::AccountId, value: BalanceOf, reward_payout: &mut BalanceOf, diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..29a395b89de4dda8aa2c56755ed941a52ff99411 --- /dev/null +++ b/frame/staking/src/testing_utils.rs @@ -0,0 +1,340 @@ +// Copyright 2020 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 . + +//! Testing utils for staking. Needs the `testing-utils` feature to be enabled. +//! +//! Note that these helpers should NOT be used with the actual crate tests, but are rather designed +//! for when the module is being externally tested (i.e. fuzzing, benchmarking, e2e tests). Enabling +//! this feature in the current crate's Cargo.toml will leak the all of this into a normal release +//! build. Just don't do it. + +use crate::*; +use codec::{Decode, Encode}; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use pallet_indices::address::Address; +use rand::Rng; +use sp_core::hashing::blake2_256; +use sp_phragmen::{ + build_support_map, evaluate_support, reduce, Assignment, PhragmenScore, StakedAssignment, +}; + +const CTRL_PREFIX: u32 = 1000; +const NOMINATOR_PREFIX: u32 = 1_000_000; + +/// A dummy suer. +pub const USER: u32 = 999_999_999; + +/// Address type of the `T` +pub type AddressOf = Address<::AccountId, u32>; + +/// Random number in the range `[a, b]`. +pub fn random(a: u32, b: u32) -> u32 { + rand::thread_rng().gen_range(a, b) +} + +/// Set the desired validator count, with related storage items. +pub fn set_validator_count(to_elect: u32) { + ValidatorCount::put(to_elect); + MinimumValidatorCount::put(to_elect / 2); + >::put(ElectionStatus::Open(T::BlockNumber::from(1u32))); +} + +/// Build an account with the given index. +pub fn account(index: u32) -> T::AccountId { + let entropy = (b"benchmark/staking", index).using_encoded(blake2_256); + T::AccountId::decode(&mut &entropy[..]).unwrap_or_default() +} + +/// Build an address given Index +pub fn address(index: u32) -> AddressOf { + pallet_indices::address::Address::Id(account::(index)) +} + +/// Generate signed origin from `who`. +pub fn signed(who: T::AccountId) -> T::Origin { + RawOrigin::Signed(who).into() +} + +/// Generate signed origin from `index`. +pub fn signed_account(index: u32) -> T::Origin { + signed::(account::(index)) +} + +/// Bond a validator. +pub fn bond_validator(stash: T::AccountId, ctrl: u32, val: BalanceOf) +where + T::Lookup: StaticLookup>, +{ + let _ = T::Currency::make_free_balance_be(&stash, val); + assert_ok!(>::bond( + signed::(stash), + address::(ctrl), + val, + RewardDestination::Controller + )); + assert_ok!(>::validate( + signed_account::(ctrl), + ValidatorPrefs::default() + )); +} + +pub fn bond_nominator( + stash: T::AccountId, + ctrl: u32, + val: BalanceOf, + target: Vec>, +) where + T::Lookup: StaticLookup>, +{ + let _ = T::Currency::make_free_balance_be(&stash, val); + assert_ok!(>::bond( + signed::(stash), + address::(ctrl), + val, + RewardDestination::Controller + )); + assert_ok!(>::nominate(signed_account::(ctrl), target)); +} + +/// Bond `nun_validators` validators and `num_nominator` nominators with `edge_per_voter` random +/// votes per nominator. +pub fn setup_chain_stakers(num_validators: u32, num_voters: u32, edge_per_voter: u32) +where + T::Lookup: StaticLookup>, +{ + (0..num_validators).for_each(|i| { + bond_validator::( + account::(i), + i + CTRL_PREFIX, + >::from(random(1, 1000)) * T::Currency::minimum_balance(), + ); + }); + + (0..num_voters).for_each(|i| { + let mut targets: Vec> = Vec::with_capacity(edge_per_voter as usize); + let mut all_targets = (0..num_validators) + .map(|t| address::(t)) + .collect::>(); + assert!(num_validators >= edge_per_voter); + (0..edge_per_voter).for_each(|_| { + let target = all_targets.remove(random(0, all_targets.len() as u32 - 1) as usize); + targets.push(target); + }); + bond_nominator::( + account::(i + NOMINATOR_PREFIX), + i + NOMINATOR_PREFIX + CTRL_PREFIX, + >::from(random(1, 1000)) * T::Currency::minimum_balance(), + targets, + ); + }); + + >::create_stakers_snapshot(); +} + +/// Build a _really bad_ but acceptable solution for election. This should always yield a solution +/// which has a less score than the seq-phragmen. +pub fn get_weak_solution( + do_reduce: bool, +) -> (Vec, CompactAssignments, PhragmenScore) { + let mut backing_stake_of: BTreeMap> = BTreeMap::new(); + + // self stake + >::enumerate().for_each(|(who, _p)| { + *backing_stake_of.entry(who.clone()).or_insert(Zero::zero()) += + >::slashable_balance_of(&who) + }); + + // add nominator stuff + >::enumerate().for_each(|(who, nomination)| { + nomination.targets.into_iter().for_each(|v| { + *backing_stake_of.entry(v).or_insert(Zero::zero()) += + >::slashable_balance_of(&who) + }) + }); + + // elect winners + let mut sorted: Vec = backing_stake_of.keys().cloned().collect(); + sorted.sort_by_key(|x| backing_stake_of.get(x).unwrap()); + let winners: Vec = sorted + .iter() + .cloned() + .take(>::validator_count() as usize) + .collect(); + + let mut staked_assignments: Vec> = Vec::new(); + >::enumerate().for_each(|(who, nomination)| { + let mut dist: Vec<(T::AccountId, ExtendedBalance)> = Vec::new(); + nomination.targets.into_iter().for_each(|v| { + if winners.iter().find(|&w| *w == v).is_some() { + dist.push((v, ExtendedBalance::zero())); + } + }); + + if dist.len() == 0 { + return; + } + + // assign real stakes. just split the stake. + let stake = , u64>>::convert( + >::slashable_balance_of(&who), + ) as ExtendedBalance; + + let mut sum: ExtendedBalance = Zero::zero(); + let dist_len = dist.len() as ExtendedBalance; + + // assign main portion + // only take the first half into account. This should highly imbalance stuff, which is good. + dist.iter_mut() + .take(if dist_len > 1 { + (dist_len as usize) / 2 + } else { + 1 + }) + .for_each(|(_, w)| { + let partial = stake / dist_len; + *w = partial; + sum += partial; + }); + + // assign the leftover to last. + let leftover = stake - sum; + let last = dist.last_mut().unwrap(); + last.1 += leftover; + + staked_assignments.push(StakedAssignment { + who, + distribution: dist, + }); + }); + + // add self support to winners. + winners.iter().for_each(|w| { + staked_assignments.push(StakedAssignment { + who: w.clone(), + distribution: vec![( + w.clone(), + , u64>>::convert( + >::slashable_balance_of(&w), + ) as ExtendedBalance, + )], + }) + }); + + if do_reduce { + reduce(&mut staked_assignments); + } + + // helpers for building the compact + let snapshot_validators = >::snapshot_validators().unwrap(); + let snapshot_nominators = >::snapshot_nominators().unwrap(); + + let nominator_index = |a: &T::AccountId| -> Option { + snapshot_nominators + .iter() + .position(|x| x == a) + .and_then(|i| >::try_into(i).ok()) + }; + let validator_index = |a: &T::AccountId| -> Option { + snapshot_validators + .iter() + .position(|x| x == a) + .and_then(|i| >::try_into(i).ok()) + }; + let stake_of = |who: &T::AccountId| -> ExtendedBalance { + , u64>>::convert( + >::slashable_balance_of(who), + ) as ExtendedBalance + }; + + // convert back to ratio assignment. This takes less space. + let low_accuracy_assignment: Vec> = + staked_assignments + .into_iter() + .map(|sa| sa.into_assignment(true)) + .collect(); + + // re-calculate score based on what the chain will decode. + let score = { + let staked: Vec> = low_accuracy_assignment + .iter() + .map(|a| { + let stake = stake_of(&a.who); + a.clone().into_staked(stake, true) + }) + .collect(); + + let (support_map, _) = + build_support_map::(winners.as_slice(), staked.as_slice()); + evaluate_support::(&support_map) + }; + + // compact encode the assignment. + let compact = CompactAssignments::from_assignment( + low_accuracy_assignment, + nominator_index, + validator_index, + ) + .unwrap(); + + // winners to index. + let winners = winners + .into_iter() + .map(|w| { + snapshot_validators + .iter() + .position(|v| *v == w) + .unwrap() + .try_into() + .unwrap() + }) + .collect::>(); + + (winners, compact, score) +} + +/// Create a solution for seq-phragmen. This uses the same internal function as used by the offchain +/// worker code. +pub fn get_seq_phragmen_solution( + do_reduce: bool, +) -> (Vec, CompactAssignments, PhragmenScore) { + let sp_phragmen::PhragmenResult { + winners, + assignments, + } = >::do_phragmen::().unwrap(); + + offchain_election::prepare_submission::(assignments, winners, do_reduce).unwrap() +} + +/// Remove all validator, nominators, votes and exposures. +pub fn clean(era: EraIndex) + where + ::AccountId: codec::EncodeLike, + u32: codec::EncodeLike, +{ + >::enumerate().for_each(|(k, _)| { + let ctrl = >::bonded(&k).unwrap(); + >::remove(&k); + >::remove(&k); + >::remove(&ctrl); + >::remove(k, era); + }); + >::enumerate().for_each(|(k, _)| >::remove(k)); + >::remove_all(); + >::remove_all(); + >::kill(); + QueuedScore::kill(); +} diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index d11457671f7c2a4cd991bdd61a717cc61191faf0..01334b918f5f9d2ecd244ed925a18cf424532979 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -18,12 +18,13 @@ use super::*; use mock::*; -use sp_runtime::{assert_eq_error_rate, traits::{OnInitialize, BadOrigin}}; +use sp_runtime::{ + assert_eq_error_rate, traits::BadOrigin, +}; use sp_staking::offence::OffenceDetails; use frame_support::{ - assert_ok, assert_noop, - traits::{Currency, ReservableCurrency}, - StorageMap, + assert_ok, assert_noop, StorageMap, + traits::{Currency, ReservableCurrency, OnInitialize}, }; use pallet_balances::Error as BalancesError; use substrate_test_utils::assert_eq_uvec; @@ -253,8 +254,6 @@ fn staking_should_work() { // --- Block 1: start_session(1); - Timestamp::set_timestamp(1); // Initialize time. - // remember + compare this along with the test. assert_eq_uvec!(validator_controllers(), vec![20, 10]); @@ -648,51 +647,37 @@ fn double_controlling_should_fail() { fn session_and_eras_work() { ExtBuilder::default().build().execute_with(|| { assert_eq!(Staking::active_era().unwrap().index, 0); + assert_eq!(Session::current_index(), 0); - // Block 1: No change. + // Session 1: No change. start_session(1); assert_eq!(Session::current_index(), 1); assert_eq!(Staking::active_era().unwrap().index, 0); - // Block 2: No change. + // Session 2: No change. start_session(2); assert_eq!(Session::current_index(), 2); assert_eq!(Staking::active_era().unwrap().index, 0); - // Block 3: Era increment. + // Session 3: Era increment. start_session(3); assert_eq!(Session::current_index(), 3); assert_eq!(Staking::active_era().unwrap().index, 1); - // Block 4: No change. + // Session 4: No change. start_session(4); assert_eq!(Session::current_index(), 4); assert_eq!(Staking::active_era().unwrap().index, 1); - // Block 5: No change. + // Session 5: No change. start_session(5); assert_eq!(Session::current_index(), 5); assert_eq!(Staking::active_era().unwrap().index, 1); - // Block 6: Era increment. + // Session 6: Era increment. start_session(6); assert_eq!(Session::current_index(), 6); assert_eq!(Staking::active_era().unwrap().index, 2); - - // Block 7: No change. - start_session(7); - assert_eq!(Session::current_index(), 7); - assert_eq!(Staking::active_era().unwrap().index, 2); - - // Block 8: No change. - start_session(8); - assert_eq!(Session::current_index(), 8); - assert_eq!(Staking::active_era().unwrap().index, 2); - - // Block 9: Era increment. - start_session(9); - assert_eq!(Session::current_index(), 9); - assert_eq!(Staking::active_era().unwrap().index, 3); }); } @@ -1052,7 +1037,6 @@ fn bond_extra_and_withdraw_unbonded_works() { assert_ne!(Staking::eras_stakers(Staking::active_era().unwrap().index, 11), Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] }); // trigger next era. - Timestamp::set_timestamp(10); start_era(2); assert_eq!(Staking::active_era().unwrap().index, 2); @@ -1722,7 +1706,6 @@ fn new_era_elects_correct_number_of_validators() { assert_eq!(Staking::validator_count(), 1); assert_eq!(validator_controllers().len(), 1); - System::set_block_number(1); Session::on_initialize(System::block_number()); assert_eq!(validator_controllers().len(), 1); @@ -1737,11 +1720,11 @@ fn phragmen_should_not_overflow_validators() { let _ = Staking::chill(Origin::signed(10)); let _ = Staking::chill(Origin::signed(20)); - bond_validator(2, u64::max_value()); - bond_validator(4, u64::max_value()); + bond_validator(3, 2, u64::max_value()); + bond_validator(5, 4, u64::max_value()); - bond_nominator(6, u64::max_value() / 2, vec![3, 5]); - bond_nominator(8, u64::max_value() / 2, vec![3, 5]); + bond_nominator(7, 6, u64::max_value() / 2, vec![3, 5]); + bond_nominator(9, 8, u64::max_value() / 2, vec![3, 5]); start_era(1); @@ -1760,11 +1743,11 @@ fn phragmen_should_not_overflow_nominators() { let _ = Staking::chill(Origin::signed(10)); let _ = Staking::chill(Origin::signed(20)); - bond_validator(2, u64::max_value() / 2); - bond_validator(4, u64::max_value() / 2); + bond_validator(3, 2, u64::max_value() / 2); + bond_validator(5, 4, u64::max_value() / 2); - bond_nominator(6, u64::max_value(), vec![3, 5]); - bond_nominator(8, u64::max_value(), vec![3, 5]); + bond_nominator(7, 6, u64::max_value(), vec![3, 5]); + bond_nominator(9, 8, u64::max_value(), vec![3, 5]); start_era(1); @@ -1779,11 +1762,11 @@ fn phragmen_should_not_overflow_nominators() { #[test] fn phragmen_should_not_overflow_ultimate() { ExtBuilder::default().nominate(false).build().execute_with(|| { - bond_validator(2, u64::max_value()); - bond_validator(4, u64::max_value()); + bond_validator(3, 2, u64::max_value()); + bond_validator(5, 4, u64::max_value()); - bond_nominator(6, u64::max_value(), vec![3, 5]); - bond_nominator(8, u64::max_value(), vec![3, 5]); + bond_nominator(7, 6, u64::max_value(), vec![3, 5]); + bond_nominator(9, 8, u64::max_value(), vec![3, 5]); start_era(1); @@ -1924,21 +1907,23 @@ fn era_is_always_same_length() { // This ensures that the sessions is always of the same length if there is no forcing no // session changes. ExtBuilder::default().build().execute_with(|| { + let session_per_era = >::get(); + start_era(1); - assert_eq!(Staking::eras_start_session_index(Staking::active_era().unwrap().index).unwrap(), SessionsPerEra::get()); + assert_eq!(Staking::eras_start_session_index(active_era()).unwrap(), session_per_era); start_era(2); - assert_eq!(Staking::eras_start_session_index(Staking::active_era().unwrap().index).unwrap(), SessionsPerEra::get() * 2); + assert_eq!(Staking::eras_start_session_index(active_era()).unwrap(), session_per_era * 2u32); let session = Session::current_index(); ForceEra::put(Forcing::ForceNew); advance_session(); advance_session(); assert_eq!(Staking::active_era().unwrap().index, 3); - assert_eq!(Staking::eras_start_session_index(Staking::active_era().unwrap().index).unwrap(), session + 2); + assert_eq!(Staking::eras_start_session_index(active_era()).unwrap(), session + 2); start_era(4); - assert_eq!(Staking::eras_start_session_index(Staking::active_era().unwrap().index).unwrap(), session + 2 + SessionsPerEra::get()); + assert_eq!(Staking::eras_start_session_index(active_era()).unwrap(), session + 2u32 + session_per_era); }); } @@ -1964,6 +1949,7 @@ fn offence_forces_new_era() { fn offence_ensures_new_era_without_clobbering() { ExtBuilder::default().build().execute_with(|| { assert_ok!(Staking::force_new_era_always(Origin::ROOT)); + assert_eq!(Staking::force_era(), Forcing::ForceAlways); on_offence_now( &[OffenceDetails { @@ -1981,10 +1967,11 @@ fn offence_ensures_new_era_without_clobbering() { } #[test] -fn offence_deselects_validator_when_slash_is_zero() { +fn offence_deselects_validator_even_when_slash_is_zero() { ExtBuilder::default().build().execute_with(|| { assert!(Session::validators().contains(&11)); assert!(>::contains_key(11)); + on_offence_now( &[OffenceDetails { offender: ( @@ -1995,9 +1982,12 @@ fn offence_deselects_validator_when_slash_is_zero() { }], &[Perbill::from_percent(0)], ); + assert_eq!(Staking::force_era(), Forcing::ForceNew); assert!(!>::contains_key(11)); + start_era(1); + assert!(!Session::validators().contains(&11)); assert!(!>::contains_key(11)); }); @@ -2038,6 +2028,7 @@ fn slash_in_old_span_does_not_deselect() { assert!(>::contains_key(11)); assert!(Session::validators().contains(&11)); + on_offence_now( &[OffenceDetails { offender: ( @@ -2048,6 +2039,7 @@ fn slash_in_old_span_does_not_deselect() { }], &[Perbill::from_percent(0)], ); + assert_eq!(Staking::force_era(), Forcing::ForceNew); assert!(!>::contains_key(11)); @@ -2075,7 +2067,7 @@ fn slash_in_old_span_does_not_deselect() { 1, ); - // not for zero-slash. + // not forcing for zero-slash and previous span. assert_eq!(Staking::force_era(), Forcing::NotForcing); assert!(>::contains_key(11)); assert!(Session::validators().contains(&11)); @@ -2096,7 +2088,7 @@ fn slash_in_old_span_does_not_deselect() { // or non-zero. assert_eq!(Staking::force_era(), Forcing::NotForcing); assert!(>::contains_key(11)); - assert!(Session::validators().contains(&11)); + assert!(Session::validators().contains(&11)); assert_ledger_consistent(11); }); } @@ -2135,7 +2127,7 @@ fn reporters_receive_their_slice() { #[test] fn subsequent_reports_in_same_span_pay_out_less() { // This test verifies that the reporters of the offence receive their slice from the slashed - // amount. + // amount, but less and less if they submit multiple reports in one span. ExtBuilder::default().build().execute_with(|| { // The reporters' reward is calculated from the total exposure. let initial_balance = 1125; @@ -2242,12 +2234,16 @@ fn dont_slash_if_fraction_is_zero() { // The validator hasn't been slashed. The new era is not forced. assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Staking::force_era(), Forcing::ForceNew); + assert_ledger_consistent(11); }); } #[test] fn only_slash_for_max_in_era() { + // multiple slashes within one era are only applied if it is more than any previous slash in the + // same era. ExtBuilder::default().build().execute_with(|| { assert_eq!(Balances::free_balance(11), 1000); @@ -2296,6 +2292,7 @@ fn only_slash_for_max_in_era() { #[test] fn garbage_collection_after_slashing() { + // ensures that `SlashingSpans` and `SpanSlash` of an account is removed after reaping. ExtBuilder::default().existential_deposit(2).build().execute_with(|| { assert_eq!(Balances::free_balance(11), 256_000); @@ -2338,27 +2335,28 @@ fn garbage_collection_after_slashing() { #[test] fn garbage_collection_on_window_pruning() { + // ensures that `ValidatorSlashInEra` and `NominatorSlashInEra` are cleared after + // `BondingDuration`. ExtBuilder::default().build().execute_with(|| { start_era(1); assert_eq!(Balances::free_balance(11), 1000); + let now = Staking::active_era().unwrap().index; - let exposure = Staking::eras_stakers(Staking::active_era().unwrap().index, 11); + let exposure = Staking::eras_stakers(now, 11); assert_eq!(Balances::free_balance(101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; on_offence_now( &[ OffenceDetails { - offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)), + offender: (11, Staking::eras_stakers(now, 11)), reporters: vec![], }, ], &[Perbill::from_percent(10)], ); - let now = Staking::active_era().unwrap().index; - assert_eq!(Balances::free_balance(11), 900); assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); @@ -2390,10 +2388,8 @@ fn slashing_nominators_by_span_max() { assert_eq!(Balances::free_balance(101), 2000); assert_eq!(Staking::slashable_balance_of(&21), 1000); - let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, 11); let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, 21); - assert_eq!(Balances::free_balance(101), 2000); let nominated_value_11 = exposure_11.others.iter().find(|o| o.who == 101).unwrap().value; let nominated_value_21 = exposure_21.others.iter().find(|o| o.who == 101).unwrap().value; @@ -2737,35 +2733,1101 @@ fn remove_multi_deferred() { let slashes = ::UnappliedSlashes::get(&1); assert_eq!(slashes.len(), 2); - println!("Slashes: {:?}", slashes); assert_eq!(slashes[0].validator, 21); assert_eq!(slashes[1].validator, 42); }) } +mod offchain_phragmen { + use crate::*; + use frame_support::{assert_noop, assert_ok}; + use sp_runtime::transaction_validity::TransactionSource; + use mock::*; + use parking_lot::RwLock; + use sp_core::offchain::{ + testing::{PoolState, TestOffchainExt, TestTransactionPoolExt}, + OffchainExt, TransactionPoolExt, + }; + use sp_io::TestExternalities; + use sp_phragmen::StakedAssignment; + use frame_support::traits::OffchainWorker; + use std::sync::Arc; + use substrate_test_utils::assert_eq_uvec; + + fn percent(x: u16) -> OffchainAccuracy { + OffchainAccuracy::from_percent(x) + } + + /// setup a new set of validators and nominator storage items independent of the parent mock + /// file. This produces a edge graph that can be reduced. + fn build_offchain_phragmen_test_ext() { + for i in (10..=40).step_by(10) { + // Note: we respect the convention of the mock (10, 11 pairs etc.) since these accounts + // have corresponding keys in session which makes everything more ergonomic and + // realistic. + bond_validator(i + 1, i, 100); + } + + let mut voter = 1; + bond_nominator(voter, 1000 + voter, 100, vec![11]); + voter = 2; + bond_nominator(voter, 1000 + voter, 100, vec![11, 11]); + voter = 3; + bond_nominator(voter, 1000 + voter, 100, vec![21, 41]); + voter = 4; + bond_nominator(voter, 1000 + voter, 100, vec![21, 31, 41]); + voter = 5; + bond_nominator(voter, 1000 + voter, 100, vec![21, 31, 41]); + } + + fn offchainify(ext: &mut TestExternalities) -> Arc> { + let (offchain, _state) = TestOffchainExt::new(); + let (pool, state) = TestTransactionPoolExt::new(); + + ext.register_extension(OffchainExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + + state + } + + #[test] + fn is_current_session_final_works() { + ExtBuilder::default() + .session_per_era(3) + .build() + .execute_with(|| { + start_era(1); + assert_eq!(Session::current_index(), 3); + assert_eq!(Staking::current_era(), Some(1)); + assert_eq!(Staking::is_current_session_final(), false); + + start_session(4); + assert_eq!(Session::current_index(), 4); + assert_eq!(Staking::current_era(), Some(1)); + assert_eq!(Staking::is_current_session_final(), true); + + start_session(5); + assert_eq!(Session::current_index(), 5); + // era changed. + assert_eq!(Staking::current_era(), Some(2)); + assert_eq!(Staking::is_current_session_final(), false); + }) + } + + #[test] + fn offchain_election_flag_is_triggered() { + ExtBuilder::default() + .session_per_era(5) + .session_length(10) + .election_lookahead(3) + .build() + .execute_with(|| { + run_to_block(7); + assert_session_era!(0, 0); + + run_to_block(10); + assert_session_era!(1, 0); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + assert!(Staking::snapshot_nominators().is_none()); + assert!(Staking::snapshot_validators().is_none()); + + run_to_block(36); + assert_session_era!(3, 0); + + // fist era has session 0, which has 0 blocks length, so we have in total 40 blocks + // in the era. + run_to_block(37); + assert_session_era!(3, 0); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37)); + assert!(Staking::snapshot_nominators().is_some()); + assert!(Staking::snapshot_validators().is_some()); + + run_to_block(38); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37)); + + run_to_block(39); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37)); + + run_to_block(40); + assert_session_era!(4, 0); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + assert!(Staking::snapshot_nominators().is_none()); + assert!(Staking::snapshot_validators().is_none()); + + run_to_block(86); + assert_session_era!(8, 1); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + assert!(Staking::snapshot_nominators().is_none()); + assert!(Staking::snapshot_validators().is_none()); + + // second era onwards has 50 blocks per era. + run_to_block(87); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(87)); + assert!(Staking::snapshot_nominators().is_some()); + assert!(Staking::snapshot_validators().is_some()); + + run_to_block(90); + assert_session_era!(9, 1); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + assert!(Staking::snapshot_nominators().is_none()); + assert!(Staking::snapshot_validators().is_none()); + }) + } + + #[test] + fn election_on_chain_fallback_works() { + ExtBuilder::default().build().execute_with(|| { + start_session(1); + start_session(2); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + // some election must have happened by now. + assert_eq!( + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let MetaEvent::staking(inner) = e { + Some(inner) + } else { + None + } + }) + .last() + .unwrap(), + RawEvent::StakingElection(ElectionCompute::OnChain), + ); + }) + } + + #[test] + #[ignore] // This takes a few mins + fn offchain_wont_work_if_snapshot_fails() { + ExtBuilder::default() + .offchain_phragmen_ext() + .election_lookahead(3) + .build() + .execute_with(|| { + run_to_block(12); + assert!(Staking::snapshot_validators().is_some()); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); + + // validate more than the limit + let limit: NominatorIndex = ValidatorIndex::max_value() as NominatorIndex + 1; + let ctrl = 1_000_000; + for i in 0..limit { + bond_validator((1000 + i).into(), (1000 + i + ctrl).into(), 100); + } + + // window stays closed since no snapshot was taken. + run_to_block(27); + assert!(Staking::snapshot_validators().is_none()); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + }) + } + + #[test] + fn staking_is_locked_when_election_window_open() { + ExtBuilder::default() + .offchain_phragmen_ext() + .election_lookahead(3) + .build() + .execute_with(|| { + run_to_block(12); + assert!(Staking::snapshot_validators().is_some()); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); + + let call = crate::Call::bond(999, 998, Default::default()); + let outer: mock::Call = call.into(); + + let lock_staking: LockStakingStatus = Default::default(); + assert_eq!( + lock_staking.validate(&10, &outer, Default::default(), Default::default(),), + TransactionValidity::Err(InvalidTransaction::Stale.into()), + ) + }) + } + + #[test] + fn signed_result_can_be_submitted() { + // should check that we have a new validator set normally, + // event says that it comes from offchain. + ExtBuilder::default() + .offchain_phragmen_ext() + .build() + .execute_with(|| { + run_to_block(12); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); + assert!(Staking::snapshot_validators().is_some()); + + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + + let queued_result = Staking::queued_elected().unwrap(); + assert_eq!(queued_result.compute, ElectionCompute::Signed); + + run_to_block(15); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + + assert_eq!( + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let MetaEvent::staking(inner) = e { + Some(inner) + } else { + None + } + }) + .last() + .unwrap(), + RawEvent::StakingElection(ElectionCompute::Signed), + ); + }) + } + + #[test] + fn signed_result_can_be_submitted_later() { + // same as `signed_result_can_be_submitted` but at a later block. + ExtBuilder::default() + .offchain_phragmen_ext() + .build() + .execute_with(|| { + run_to_block(14); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); + + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + + let queued_result = Staking::queued_elected().unwrap(); + assert_eq!(queued_result.compute, ElectionCompute::Signed); + + run_to_block(15); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + + assert_eq!( + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let MetaEvent::staking(inner) = e { + Some(inner) + } else { + None + } + }) + .last() + .unwrap(), + RawEvent::StakingElection(ElectionCompute::Signed), + ); + }) + } + + #[test] + fn early_solution_submission_is_rejected() { + // should check that we have a new validator set normally, + // event says that it comes from offchain. + ExtBuilder::default() + .offchain_phragmen_ext() + .build() + .execute_with(|| { + run_to_block(11); + // submission is not yet allowed + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + + // create all the indices just to build the solution. + Staking::create_stakers_snapshot(); + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + Staking::kill_stakers_snapshot(); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenEarlySubmission, + ); + }) + } + + #[test] + fn weak_solution_is_rejected() { + // A solution which is weaker than what we currently have on-chain is rejected. + ExtBuilder::default() + .offchain_phragmen_ext() + .has_stakers(false) + .validator_count(4) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + // a good solution + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + + // a bad solution + let (compact, winners, score) = horrible_phragmen_with_post_processing(false); + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenWeakSubmission, + ); + }) + } + + #[test] + fn better_solution_is_accepted() { + // A solution which is better than what we currently have on-chain is accepted. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + // a meeeeh solution + let (compact, winners, score) = horrible_phragmen_with_post_processing(false); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + + // a better solution + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + }) + } + + #[test] + fn offchain_worker_runs_when_window_open() { + // at the end of the first finalized block with ElectionStatus::open(_), it should execute. + let mut ext = ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(2) + .build(); + let state = offchainify(&mut ext); + ext.execute_with(|| { + run_to_block(12); + + // local key 11 is in the elected set. + assert_eq_uvec!(Session::validators(), vec![11, 21]); + assert_eq!(state.read().transactions.len(), 0); + Staking::offchain_worker(12); + assert_eq!(state.read().transactions.len(), 1); + + let encoded = state.read().transactions[0].clone(); + let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); + + let call = extrinsic.call; + let inner = match call { + mock::Call::Staking(inner) => inner, + }; + + assert_eq!( + ::validate_unsigned( + TransactionSource::Local, + &inner, + ), + TransactionValidity::Ok(ValidTransaction { + priority: 1125, // the proposed slot stake. + requires: vec![], + provides: vec![("StakingOffchain", active_era()).encode()], + longevity: 3, + propagate: false, + }) + ) + }) + } + + #[test] + fn mediocre_submission_from_authority_is_early_rejected() { + let mut ext = ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .build(); + let state = offchainify(&mut ext); + ext.execute_with(|| { + run_to_block(12); + // put a good solution on-chain + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ),); + + // now run the offchain worker in the same chain state. + Staking::offchain_worker(12); + assert_eq!(state.read().transactions.len(), 1); + + let encoded = state.read().transactions[0].clone(); + let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); + + let call = extrinsic.call; + let inner = match call { + mock::Call::Staking(inner) => inner, + }; + + // pass this call to ValidateUnsigned + assert_eq!( + ::validate_unsigned( + TransactionSource::Local, + &inner, + ), + TransactionValidity::Err( + InvalidTransaction::Custom(>::PhragmenWeakSubmission.as_u8()).into(), + ), + ) + }) + } + + #[test] + fn invalid_phragmen_result_correct_number_of_winners() { + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + ValidatorCount::put(3); + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + ValidatorCount::put(4); + + assert_eq!(winners.len(), 3); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusWinnerCount, + ); + }) + } + + #[test] + fn invalid_phragmen_result_correct_number_of_winners_1() { + // if we have too little validators, then the number of candidates is the bound. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(8) // we simply cannot elect 8 + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + ValidatorCount::put(3); + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + ValidatorCount::put(4); + + assert_eq!(winners.len(), 3); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusWinnerCount, + ); + }) + } + + #[test] + fn invalid_phragmen_result_correct_number_of_winners_2() { + // if we have too little validators, then the number of candidates is the bound. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(8) // we simply cannot elect 8 + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + + assert_eq!(winners.len(), 4); + + // all good. We chose 4 and it works. + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ),); + }) + } + + #[test] + fn invalid_phragmen_result_out_of_bound_nominator_index() { + // A nominator index which is simply invalid + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4); + assert_eq!(Staking::snapshot_validators().unwrap().len(), 4); + let (mut compact, winners, score) = prepare_submission_with(true, |_| {}); + + // index 9 doesn't exist. + compact.votes1.push((9, 2)); + + // The error type sadly cannot be more specific now. + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusCompact, + ); + }) + } + + #[test] + fn invalid_phragmen_result_out_of_bound_validator_index() { + // A validator index which is out of bound + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4); + assert_eq!(Staking::snapshot_validators().unwrap().len(), 4); + let (mut compact, winners, score) = prepare_submission_with(true, |_| {}); + + // index 4 doesn't exist. + compact.votes1.push((3, 4)); + + // The error type sadly cannot be more specific now. + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusCompact, + ); + }) + } + + #[test] + fn invalid_phragmen_result_out_of_bound_winner_index() { + // A winner index which is simply invalid + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4); + assert_eq!(Staking::snapshot_validators().unwrap().len(), 4); + let (compact, _, score) = prepare_submission_with(true, |_| {}); + + // index 4 doesn't exist. + let winners = vec![0, 1, 2, 4]; + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusWinner, + ); + }) + } + + #[test] + fn invalid_phragmen_result_non_winner_validator_index() { + // An edge that points to a correct validator index who is NOT a winner. This is very + // similar to the test that raises `PhragmenBogusNomination`. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(2) // we select only 2. + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4); + assert_eq!(Staking::snapshot_validators().unwrap().len(), 4); + let (compact, winners, score) = prepare_submission_with(true, |a| { + a.iter_mut() + .find(|x| x.who == 5) + // all 3 cannot be among the winners. Although, all of them are validator + // candidates. + .map(|x| x.distribution = vec![(21, 50), (41, 30), (31, 20)]); + }); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusEdge, + ); + }) + } + + #[test] + fn invalid_phragmen_result_wrong_self_vote() { + // A self vote for someone else. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + let (compact, winners, score) = prepare_submission_with(true, |a| { + // mutate a self vote to target someone else. That someone else is still among the + // winners + a.iter_mut().find(|x| x.who == 11).map(|x| { + x.distribution + .iter_mut() + .find(|y| y.0 == 11) + .map(|y| y.0 = 21) + }); + }); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusSelfVote, + ); + }) + } + + #[test] + fn invalid_phragmen_result_wrong_self_vote_2() { + // A self validator voting for someone else next to self vote. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + let (compact, winners, score) = prepare_submission_with(true, |a| { + // Remove the self vote. + a.retain(|x| x.who != 11); + // add is as a new double vote + a.push(StakedAssignment { + who: 11, + distribution: vec![(11, 50), (21, 50)], + }); + }); + + // This raises score issue. + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusSelfVote, + ); + }) + } + + #[test] + fn invalid_phragmen_result_over_stake() { + // Someone's edge ratios sums to more than 100%. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + // Note: we don't reduce here to be able to tweak votes3. votes3 will vanish if you + // reduce. + let (mut compact, winners, score) = prepare_submission_with(false, |_| {}); + + if let Some(c) = compact.votes3.iter_mut().find(|x| x.0 == 0) { + // by default it should have been (0, [(2, 33%), (1, 33%)], 0) + // now the sum is above 100% + c.1 = [(2, percent(66)), (1, percent(66))]; + } + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusCompact, + ); + }) + } + + #[test] + fn invalid_phragmen_result_under_stake() { + // at the time of this writing, we cannot under stake someone. The compact assignment works + // in a way that some of the stakes are presented by the submitter, and the last one is read + // from chain by subtracting the rest from total. Hence, the sum is always correct. + // This test is only here as a demonstration. + } + + #[test] + fn invalid_phragmen_result_invalid_target_stealing() { + // A valid voter who voted for someone who is a candidate, and is a correct winner, but is + // actually NOT nominated by this nominator. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + let (compact, winners, score) = prepare_submission_with(false, |a| { + // 3 only voted for 20 and 40. We add a fake vote to 30. The stake sum is still + // correctly 100. + a.iter_mut() + .find(|x| x.who == 3) + .map(|x| x.distribution = vec![(21, 50), (41, 30), (31, 20)]); + }); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusNomination, + ); + }) + } + + #[test] + fn nomination_slash_filter_is_checked() { + // If a nominator has voted for someone who has been recently slashed, that particular + // nomination should be disabled for the upcoming election. A solution must respect this + // rule. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + + // finalize the round with fallback. This is needed since all nominator submission + // are in era zero and we want this one to pass with no problems. + run_to_block(15); + + // go to the next session to trigger start_era and bump the active era + run_to_block(20); + + // slash 10. This must happen outside of the election window. + let offender_expo = Staking::eras_stakers(active_era(), 11); + on_offence_now( + &[OffenceDetails { + offender: (11, offender_expo.clone()), + reporters: vec![], + }], + &[Perbill::from_percent(50)], + ); + + // validate 10 again for the next round. But this guy will not have the votes that + // it should have had from 1 and 2. + assert_ok!(Staking::validate( + Origin::signed(10), + Default::default() + )); + + // open the election window and create snapshots. + run_to_block(32); + + // a solution that has been prepared after the slash. + let (compact, winners, score) = prepare_submission_with(false, |a| { + // no one is allowed to vote for 10, except for itself. + a.into_iter() + .filter(|s| s.who != 11) + .for_each(|s| + assert!(s.distribution.iter().find(|(t, _)| *t == 11).is_none()) + ); + }); + + // can be submitted. + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + + // a wrong solution. + let (compact, winners, score) = prepare_submission_with(false, |a| { + // add back the vote that has been filtered out. + a.push(StakedAssignment { + who: 1, + distribution: vec![(11, 100)] + }); + }); + + // is rejected. + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenSlashedNomination, + ); + }) + } + + #[test] + fn invalid_phragmen_result_wrong_score() { + // A valid voter who's total distributed stake is more than what they bond + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + let (compact, winners, mut score) = prepare_submission_with(true, |_| {}); + score[0] += 1; + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusScore, + ); + }) + } + + #[test] + fn offchain_storage_is_set() { + let mut ext = ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .build(); + let state = offchainify(&mut ext); + + ext.execute_with(|| { + use offchain_election::OFFCHAIN_HEAD_DB; + use sp_runtime::offchain::storage::StorageValueRef; + + run_to_block(12); + + Staking::offchain_worker(12); + // it works + assert_eq!(state.read().transactions.len(), 1); + + // and it is set + let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB); + assert_eq!(storage.get::().unwrap().unwrap(), 12); + }) + } + + #[test] + fn offchain_storage_prevents_duplicate() { + let mut ext = ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .build(); + let _ = offchainify(&mut ext); + + ext.execute_with(|| { + use offchain_election::OFFCHAIN_HEAD_DB; + use sp_runtime::offchain::storage::StorageValueRef; + let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB); + + run_to_block(12); + + // first run -- ok + assert_eq!( + offchain_election::set_check_offchain_execution_status::(12), + Ok(()), + ); + assert_eq!(storage.get::().unwrap().unwrap(), 12); + + // re-execute after the next. not allowed. + assert_eq!( + offchain_election::set_check_offchain_execution_status::(13), + Err("recently executed."), + ); + + // a fork like situation -- re-execute 10, 11, 12. But it won't go through. + assert_eq!( + offchain_election::set_check_offchain_execution_status::(10), + Err("fork."), + ); + assert_eq!( + offchain_election::set_check_offchain_execution_status::(11), + Err("fork."), + ); + assert_eq!( + offchain_election::set_check_offchain_execution_status::(12), + Err("recently executed."), + ); + }) + } + + #[test] + #[should_panic] + fn offence_is_blocked_when_window_open() { + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + run_to_block(12); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); + + let offender_expo = Staking::eras_stakers(active_era(), 10); + + // panic from the impl in mock + on_offence_now( + &[OffenceDetails { + offender: (10, offender_expo.clone()), + reporters: vec![], + }], + &[Perbill::from_percent(10)], + ); + }) + } +} + #[test] -fn slash_kicks_validators_not_nominators() { +fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_validator() { ExtBuilder::default().build().execute_with(|| { start_era(1); + assert_eq_uvec!(Session::validators(), vec![11, 21]); + // pre-slash balance assert_eq!(Balances::free_balance(11), 1000); - - let exposure = Staking::eras_stakers(Staking::active_era().unwrap().index, 11); assert_eq!(Balances::free_balance(101), 2000); - let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; + + // 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); + + assert_eq!(exposure_11.total, 1000 + 125); + assert_eq!(exposure_21.total, 1000 + 375); on_offence_now( - &[ - OffenceDetails { - offender: (11, exposure.clone()), - reporters: vec![], - }, - ], + &[OffenceDetails { + offender: (11, exposure_11.clone()), + reporters: vec![], + }], &[Perbill::from_percent(10)], ); + // post-slash balance + let nominator_slash_amount_11 = 125 / 10; assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); + 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. @@ -2777,8 +3839,23 @@ fn slash_kicks_validators_not_nominators() { // 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(); + let last_slash = ::SlashingSpans::get(&11) + .unwrap() + .last_nonzero_slash(); assert!(nominations.submitted_in < last_slash); + + // actually re-bond the slashed validator + assert_ok!(Staking::validate(Origin::signed(10), Default::default())); + + start_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); + + // 20 is re-elected, with the (almost) entire support of 100 + assert_eq!(exposure_21.total, 1000 + 500 - nominator_slash_amount_11); }); } @@ -2891,7 +3968,7 @@ fn zero_slash_keeps_nominators() { assert_eq!(Balances::free_balance(11), 1000); - let exposure = Staking::eras_stakers(Staking::active_era().unwrap().index, 11); + let exposure = Staking::eras_stakers(active_era(), 11); assert_eq!(Balances::free_balance(101), 2000); on_offence_now( @@ -3039,3 +4116,11 @@ fn set_history_depth_works() { assert!(!::ErasTotalStake::contains_key(10 - 5)); }); } + +#[test] +fn assert_migration_is_noop() { + let kusama_active_era = "4a0200000190e2721171010000"; + let era = ActiveEraInfo::decode(&mut &hex::decode(kusama_active_era).unwrap()[..]).unwrap(); + assert_eq!(era.index, 586); + assert_eq!(era.start, Some(1585135674000)); +} diff --git a/frame/sudo/Cargo.toml b/frame/sudo/Cargo.toml index 8d645df85cb9457934099f2f05029b64ecee4e30..4216b94ec12f12ccf8514698a78a25e3e9752ac6 100644 --- a/frame/sudo/Cargo.toml +++ b/frame/sudo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-sudo" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,15 +10,15 @@ description = "FRAME pallet for sudo" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } [features] default = ["std"] @@ -31,3 +31,6 @@ std = [ "frame-support/std", "frame-system/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/sudo/src/lib.rs b/frame/sudo/src/lib.rs index 8ee09ba223ab1a393fadb9ff41056dbac21c756b..462a84c0a17b2f0aa867c7a424a08c773d83da4b 100644 --- a/frame/sudo/src/lib.rs +++ b/frame/sudo/src/lib.rs @@ -58,6 +58,7 @@ //! //! decl_module! { //! pub struct Module for enum Call where origin: T::Origin { +//! #[weight = frame_support::weights::SimpleDispatchInfo::default()] //! pub fn privileged_function(origin) -> dispatch::DispatchResult { //! ensure_root(origin)?; //! @@ -120,7 +121,7 @@ decl_module! { /// - Weight of derivative `call` execution + 10,000. /// # #[weight = FunctionOf( - |args: (&Box<::Call>,)| args.0.get_dispatch_info().weight + 10_000, + |args: (&Box<::Call>,)| args.0.get_dispatch_info().weight + 10_000, |args: (&Box<::Call>,)| args.0.get_dispatch_info().class, true )] @@ -150,6 +151,7 @@ decl_module! { /// - Limited storage reads. /// - One DB change. /// # + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn set_key(origin, new: ::Source) { // This is a public call, so we ensure that the origin is some signed account. let sender = ensure_signed(origin)?; @@ -174,7 +176,7 @@ decl_module! { #[weight = FunctionOf( |args: (&::Source, &Box<::Call>,)| { args.1.get_dispatch_info().weight + 10_000 - }, + }, |args: (&::Source, &Box<::Call>,)| { args.1.get_dispatch_info().class }, diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index 8f0dd255acbd9495694245fe0854d3195fd99955..3bad72a115716b6f5c1f6297bf62ef40d24674a2 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-support" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,25 +11,25 @@ description = "Support code for the runtime." [dependencies] log = "0.4" serde = { version = "1.0.101", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -frame-metadata = { version = "11.0.0-alpha.2", default-features = false, path = "../metadata" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.2"} -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-arithmetic = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/arithmetic" } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/inherents" } -frame-support-procedural = { version = "2.0.0-alpha.2", path = "./procedural" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +frame-metadata = { version = "11.0.0-alpha.5", default-features = false, path = "../metadata" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-arithmetic = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/arithmetic" } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" } +frame-support-procedural = { version = "2.0.0-alpha.5", path = "./procedural" } paste = "0.1.6" once_cell = { version = "1", default-features = false, optional = true } -sp-state-machine = { version = "0.8.0-alpha.2", optional = true, path = "../../primitives/state-machine" } +sp-state-machine = { version = "0.8.0-alpha.5", optional = true, path = "../../primitives/state-machine" } bitmask = { version = "0.5.0", default-features = false } impl-trait-for-tuples = "0.1.3" tracing = { version = "0.1.10", optional = true } [dev-dependencies] pretty_assertions = "0.6.1" -frame-system = { version = "2.0.0-alpha.2", path = "../system" } +frame-system = { version = "2.0.0-alpha.5", path = "../system" } [features] default = ["std"] @@ -49,3 +49,7 @@ std = [ ] nightly = [] strict = [] +runtime-benchmarks = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/support/procedural/Cargo.toml b/frame/support/procedural/Cargo.toml index 8d8ecb18a761b94c22d16267531cb328cf7b7a35..2f7450e4b86d3ea960a9b9b2e1e3b2c32b1af306 100644 --- a/frame/support/procedural/Cargo.toml +++ b/frame/support/procedural/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-support-procedural" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,7 +12,10 @@ description = "Proc macro of Support code for the runtime." proc-macro = true [dependencies] -frame-support-procedural-tools = { version = "2.0.0-alpha.2", path = "./tools" } +frame-support-procedural-tools = { version = "2.0.0-alpha.5", path = "./tools" } proc-macro2 = "1.0.6" quote = "1.0.3" syn = { version = "1.0.7", features = ["full"] } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 942a47a533e5f894425d9eb60def32c51ed73ff6..b74a27e7ba936c982512438fc47d5ab7dd5dc379 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -89,7 +89,7 @@ fn construct_runtime_parsed(definition: RuntimeDefinition) -> Result Result( diff --git a/frame/support/procedural/src/storage/genesis_config/mod.rs b/frame/support/procedural/src/storage/genesis_config/mod.rs index 09afcb9a60d78ae263c162ac011819c746ff9c20..eeeca150d9b9b8419922f73f1ef51ed3ce46e095 100644 --- a/frame/support/procedural/src/storage/genesis_config/mod.rs +++ b/frame/support/procedural/src/storage/genesis_config/mod.rs @@ -66,6 +66,7 @@ fn decl_genesis_config_and_impl_default( let genesis_where_clause = &genesis_config.genesis_where_clause; quote!( + /// Genesis config for the module, allow to build genesis storage. #[derive(#scrate::Serialize, #scrate::Deserialize)] #[cfg(feature = "std")] #[serde(rename_all = "camelCase")] @@ -138,6 +139,7 @@ fn impl_build_storage( quote!{ #[cfg(feature = "std")] 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, String diff --git a/frame/support/procedural/tools/Cargo.toml b/frame/support/procedural/tools/Cargo.toml index 52773f6fbeea5a235c1b816494057774a0045dfa..f199f1245db0499aa90425ac6da3966fa2132201 100644 --- a/frame/support/procedural/tools/Cargo.toml +++ b/frame/support/procedural/tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-support-procedural-tools" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,8 +9,11 @@ repository = "https://github.com/paritytech/substrate/" description = "Proc macro helpers for procedural macros" [dependencies] -frame-support-procedural-tools-derive = { version = "2.0.0-alpha.2", path = "./derive" } +frame-support-procedural-tools-derive = { version = "2.0.0-alpha.5", path = "./derive" } proc-macro2 = "1.0.6" quote = "1.0.3" syn = { version = "1.0.7", features = ["full", "visit"] } proc-macro-crate = "0.1.4" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/support/procedural/tools/derive/Cargo.toml b/frame/support/procedural/tools/derive/Cargo.toml index 6bed290c7dede02e1e12827d87a2d254d5360013..bf6346ab1b67a63c57fb021550dec9a8fda0420a 100644 --- a/frame/support/procedural/tools/derive/Cargo.toml +++ b/frame/support/procedural/tools/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-support-procedural-tools-derive" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -15,3 +15,6 @@ proc-macro = true proc-macro2 = "1.0.6" quote = { version = "1.0.3", features = ["proc-macro"] } syn = { version = "1.0.7", features = ["proc-macro" ,"full", "extra-traits", "parsing"] } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index a9c48097ad64ab6ecb28a10c7f7c66dd7b30cf18..dadeb5cf82ad8b1616a73e58f97455b87da0495a 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -25,7 +25,7 @@ pub use frame_metadata::{ }; pub use crate::weights::{ SimpleDispatchInfo, GetDispatchInfo, DispatchInfo, WeighData, ClassifyDispatch, - TransactionPriority, Weight, WeighBlock, PaysFee, + TransactionPriority, Weight, PaysFee, }; pub use sp_runtime::{traits::Dispatchable, DispatchError, DispatchResult}; pub use crate::traits::{CallMetadata, GetCallMetadata, GetCallName}; @@ -57,12 +57,14 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {} /// # #[macro_use] /// # extern crate frame_support; /// # use frame_support::dispatch; +/// # use frame_support::weights::SimpleDispatchInfo; /// # use frame_system::{self as system, Trait, ensure_signed}; /// decl_module! { /// pub struct Module for enum Call where origin: T::Origin { /// /// // Private functions are dispatchable, but not available to other /// // FRAME pallets. +/// #[weight = SimpleDispatchInfo::default()] /// fn my_function(origin, var: u64) -> dispatch::DispatchResult { /// // Your implementation /// Ok(()) @@ -70,6 +72,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {} /// /// // Public functions are both dispatchable and available to other /// // FRAME pallets. +/// #[weight = SimpleDispatchInfo::default()] /// pub fn my_public_function(origin) -> dispatch::DispatchResult { /// // Your implementation /// Ok(()) @@ -97,15 +100,17 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {} /// # #[macro_use] /// # extern crate frame_support; /// # use frame_support::dispatch; +/// # use frame_support::weights::SimpleDispatchInfo; /// # use frame_system::{self as system, Trait, ensure_signed}; /// decl_module! { /// pub struct Module for enum Call where origin: T::Origin { -/// +/// #[weight = SimpleDispatchInfo::default()] /// fn my_long_function(origin) -> dispatch::DispatchResult { /// // Your implementation /// Ok(()) /// } /// +/// #[weight = SimpleDispatchInfo::default()] /// fn my_short_function(origin) { /// // Your implementation /// } @@ -122,9 +127,11 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {} /// # #[macro_use] /// # extern crate frame_support; /// # use frame_support::dispatch; +/// # use frame_support::weights::SimpleDispatchInfo; /// # use frame_system::{self as system, Trait, ensure_signed, ensure_root}; /// decl_module! { /// pub struct Module for enum Call where origin: T::Origin { +/// #[weight = SimpleDispatchInfo::default()] /// fn my_privileged_function(origin) -> dispatch::DispatchResult { /// ensure_root(origin)?; /// // Your implementation @@ -200,13 +207,23 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {} /// is a runtime upgrade. This allows each module to upgrade its storage before the storage items are used. /// As such, **calling other modules must be avoided**!! Using this function will implement the /// [`OnRuntimeUpgrade`](../sp_runtime/traits/trait.OnRuntimeUpgrade.html) trait. +/// Function signature must be `fn on_runtime_upgrade() -> frame_support::weights::Weight`. +/// /// * `on_initialize`: Executes at the beginning of a block. Using this function will -/// implement the [`OnInitialize`](../sp_runtime/traits/trait.OnInitialize.html) trait. +/// implement the [`OnInitialize`](./trait.OnInitialize.html) trait. +/// Function signature can be either: +/// * `fn on_initialize(n: BlockNumber) -> frame_support::weights::Weight` or +/// * `fn on_initialize() -> frame_support::weights::Weight` +/// /// * `on_finalize`: Executes at the end of a block. Using this function will -/// implement the [`OnFinalize`](../sp_runtime/traits/trait.OnFinalize.html) trait. +/// implement the [`OnFinalize`](./traits/trait.OnFinalize.html) trait. +/// Function signature can be either: +/// * `fn on_finalize(n: BlockNumber) -> frame_support::weights::Weight` or +/// * `fn on_finalize() -> frame_support::weights::Weight` +/// /// * `offchain_worker`: Executes at the beginning of a block and produces extrinsics for a future block /// upon completion. Using this function will implement the -/// [`OffchainWorker`](../sp_runtime/traits/trait.OffchainWorker.html) trait. +/// [`OffchainWorker`](./traits/trait.OffchainWorker.html) trait. #[macro_export] macro_rules! decl_module { // Entry point #1. @@ -327,7 +344,7 @@ macro_rules! decl_module { "`deposit_event` function is reserved and must follow the syntax: `$vis:vis fn deposit_event() = default;`" ); }; - // Add on_finalize, without a given weight. + // Add on_finalize (@normalize $(#[$attr:meta])* pub struct $mod_type:ident<$trait_instance:ident: $trait_name:ident$(, I: $instantiable:path $(= $module_default_instance:path)?)?> @@ -354,7 +371,6 @@ macro_rules! decl_module { { $( $on_initialize )* } { $( $on_runtime_upgrade )* } { - #[weight = $crate::dispatch::SimpleDispatchInfo::zero()] fn on_finalize( $( $param_name : $param ),* ) { $( $impl )* } } { $( $offchain )* } @@ -364,7 +380,7 @@ macro_rules! decl_module { $($rest)* ); }; - // Add on_finalize, given weight. + // compile_error on_finalize, given weight removed syntax. (@normalize $(#[$attr:meta])* pub struct $mod_type:ident<$trait_instance:ident: $trait_name:ident$(, I: $instantiable:path $(= $module_default_instance:path)?)?> @@ -383,26 +399,12 @@ macro_rules! decl_module { fn on_finalize( $( $param_name:ident : $param:ty ),* $(,)? ) { $( $impl:tt )* } $($rest:tt)* ) => { - $crate::decl_module!(@normalize - $(#[$attr])* - pub struct $mod_type<$trait_instance: $trait_name$(, I: $instantiable $(= $module_default_instance)?)?> - for enum $call_type where origin: $origin_type, system = $system - { $( $other_where_bounds )* } - { $( $deposit_event )* } - { $( $on_initialize )* } - { $( $on_runtime_upgrade )* } - { - #[weight = $weight] - fn on_finalize( $( $param_name : $param ),* ) { $( $impl )* } - } - { $( $offchain )* } - { $( $constants )* } - { $( $error_type )* } - [ $( $dispatchables )* ] - $($rest)* + compile_error!( + "`on_finalize` can't be given weight attribute anymore, weight must be returned by \ + `on_initialize` or `on_runtime_upgrade` instead" ); }; - // Add on_runtime_upgrade, without a given weight. + // compile_error on_runtime_upgrade, without a given weight removed syntax. (@normalize $(#[$attr:meta])* pub struct $mod_type:ident< @@ -422,26 +424,11 @@ macro_rules! decl_module { fn on_runtime_upgrade( $( $param_name:ident : $param:ty ),* $(,)? ) { $( $impl:tt )* } $($rest:tt)* ) => { - $crate::decl_module!(@normalize - $(#[$attr])* - pub struct $mod_type<$trait_instance: $trait_name$(, I: $instantiable $(= $module_default_instance)?)?> - for enum $call_type where origin: $origin_type, system = $system - { $( $other_where_bounds )* } - { $( $deposit_event )* } - { $( $on_initialize )* } - { - #[weight = $crate::dispatch::SimpleDispatchInfo::zero()] - fn on_runtime_upgrade( $( $param_name : $param ),* ) { $( $impl )* } - } - { $( $on_finalize )* } - { $( $offchain )* } - { $( $constants )* } - { $( $error_type )* } - [ $( $dispatchables )* ] - $($rest)* + compile_error!( + "`on_runtime_upgrade` must return Weight, signature has changed." ); }; - // Add on_runtime_upgrade, given weight. + // compile_error on_runtime_upgrade, given weight removed syntax. (@normalize $(#[$attr:meta])* pub struct $mod_type:ident< @@ -461,6 +448,31 @@ macro_rules! decl_module { #[weight = $weight:expr] fn on_runtime_upgrade( $( $param_name:ident : $param:ty ),* $(,)? ) { $( $impl:tt )* } $($rest:tt)* + ) => { + compile_error!( + "`on_runtime_upgrade` can't be given weight attribute anymore, weight must be returned \ + by the function directly." + ); + }; + // Add on_runtime_upgrade + (@normalize + $(#[$attr:meta])* + pub struct $mod_type:ident< + $trait_instance:ident: $trait_name:ident$(, I: $instantiable:path $(= $module_default_instance:path)?)? + > + for enum $call_type:ident where origin: $origin_type:ty, system = $system:ident + { $( $other_where_bounds:tt )* } + { $( $deposit_event:tt )* } + { $( $on_initialize:tt )* } + {} + { $( $on_finalize:tt )* } + { $( $offchain:tt )* } + { $( $constants:tt )* } + { $( $error_type:tt )* } + [ $( $dispatchables:tt )* ] + $(#[doc = $doc_attr:tt])* + fn on_runtime_upgrade( $( $param_name:ident : $param:ty ),* $(,)? ) -> $return:ty { $( $impl:tt )* } + $($rest:tt)* ) => { $crate::decl_module!(@normalize $(#[$attr])* @@ -470,8 +482,7 @@ macro_rules! decl_module { { $( $deposit_event )* } { $( $on_initialize )* } { - #[weight = $weight] - fn on_runtime_upgrade( $( $param_name : $param ),* ) { $( $impl )* } + fn on_runtime_upgrade( $( $param_name : $param ),* ) -> $return { $( $impl )* } } { $( $on_finalize )* } { $( $offchain )* } @@ -481,7 +492,7 @@ macro_rules! decl_module { $($rest)* ); }; - // Add on_initialize, without a given weight. + // compile_error on_initialize, without a given weight removed syntax. (@normalize $(#[$attr:meta])* pub struct $mod_type:ident< @@ -501,26 +512,11 @@ macro_rules! decl_module { fn on_initialize( $( $param_name:ident : $param:ty ),* $(,)? ) { $( $impl:tt )* } $($rest:tt)* ) => { - $crate::decl_module!(@normalize - $(#[$attr])* - pub struct $mod_type<$trait_instance: $trait_name$(, I: $instantiable $(= $module_default_instance)?)?> - for enum $call_type where origin: $origin_type, system = $system - { $( $other_where_bounds )* } - { $( $deposit_event )* } - { - #[weight = $crate::dispatch::SimpleDispatchInfo::zero()] - fn on_initialize( $( $param_name : $param ),* ) { $( $impl )* } - } - { $( $on_runtime_upgrade )* } - { $( $on_finalize )* } - { $( $offchain )* } - { $( $constants )* } - { $( $error_type )* } - [ $( $dispatchables )* ] - $($rest)* + compile_error!( + "`on_initialize` must return Weight, signature has changed." ); }; - // Add on_initialize, given weight. + // compile_error on_initialize, with given weight removed syntax. (@normalize $(#[$attr:meta])* pub struct $mod_type:ident< @@ -540,6 +536,31 @@ macro_rules! decl_module { #[weight = $weight:expr] fn on_initialize( $( $param_name:ident : $param:ty ),* $(,)? ) { $( $impl:tt )* } $($rest:tt)* + ) => { + compile_error!( + "`on_initialize` can't be given weight attribute anymore, weight must be returned \ + by the function directly." + ); + }; + // Add on_initialize + (@normalize + $(#[$attr:meta])* + pub struct $mod_type:ident< + $trait_instance:ident: $trait_name:ident$(, I: $instantiable:path $(= $module_default_instance:path)?)? + > + for enum $call_type:ident where origin: $origin_type:ty, system = $system:ident + { $( $other_where_bounds:tt )* } + { $( $deposit_event:tt )* } + {} + { $( $on_runtime_upgrade:tt )* } + { $( $on_finalize:tt )* } + { $( $offchain:tt )* } + { $( $constants:tt )* } + { $( $error_type:tt )* } + [ $( $dispatchables:tt )* ] + $(#[doc = $doc_attr:tt])* + fn on_initialize( $( $param_name:ident : $param:ty ),* $(,)? ) -> $return:ty { $( $impl:tt )* } + $($rest:tt)* ) => { $crate::decl_module!(@normalize $(#[$attr])* @@ -548,8 +569,7 @@ macro_rules! decl_module { { $( $other_where_bounds )* } { $( $deposit_event )* } { - #[weight = $weight] - fn on_initialize( $( $param_name : $param ),* ) { $( $impl )* } + fn on_initialize( $( $param_name : $param ),* ) -> $return { $( $impl )* } } { $( $on_runtime_upgrade )* } { $( $on_finalize )* } @@ -794,27 +814,10 @@ macro_rules! decl_module { ) $( -> $result:ty )* { $( $impl:tt )* } $($rest:tt)* ) => { - $crate::decl_module!(@normalize - $(#[$attr])* - pub struct $mod_type< - $trait_instance: $trait_name$(, $instance: $instantiable $(= $module_default_instance)?)? - > - for enum $call_type where origin: $origin_type, system = $system - { $( $other_where_bounds )* } - { $( $deposit_event )* } - { $( $on_initialize )* } - { $( $on_runtime_upgrade )* } - { $( $on_finalize )* } - { $( $offchain )* } - { $( $constants )* } - { $( $error_type )* } - [ $( $dispatchables )* ] - $(#[doc = $doc_attr])* - #[weight = $crate::dispatch::SimpleDispatchInfo::default()] - $fn_vis fn $fn_name( - $from $(, $(#[$codec_attr])* $param_name : $param )* - ) $( -> $result )* { $( $impl )* } - $($rest)* + compile_error!(concat!( + "Missing weight for ", stringify!($ident), + ". Every dispatchable must have a #[weight] attribute." + ) ); }; // Ignore any ident which is not `origin` with type `T::Origin`. @@ -965,14 +968,13 @@ macro_rules! decl_module { (@impl_on_initialize $module:ident<$trait_instance:ident: $trait_name:ident$(, $instance:ident: $instantiable:path)?>; { $( $other_where_bounds:tt )* } - #[weight = $weight:expr] - fn on_initialize() { $( $impl:tt )* } + fn on_initialize() -> $return:ty { $( $impl:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OnInitialize<$trait_instance::BlockNumber> + $crate::traits::OnInitialize<$trait_instance::BlockNumber> for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* { - fn on_initialize(_block_number_not_used: $trait_instance::BlockNumber) { + fn on_initialize(_block_number_not_used: $trait_instance::BlockNumber) -> $return { use $crate::sp_std::if_std; if_std! { use $crate::tracing; @@ -987,14 +989,13 @@ macro_rules! decl_module { (@impl_on_initialize $module:ident<$trait_instance:ident: $trait_name:ident$(, $instance:ident: $instantiable:path)?>; { $( $other_where_bounds:tt )* } - #[weight = $weight:expr] - fn on_initialize($param:ident : $param_ty:ty) { $( $impl:tt )* } + fn on_initialize($param:ident : $param_ty:ty) -> $return:ty { $( $impl:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OnInitialize<$trait_instance::BlockNumber> + $crate::traits::OnInitialize<$trait_instance::BlockNumber> for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* { - fn on_initialize($param: $param_ty) { + fn on_initialize($param: $param_ty) -> $return { use $crate::sp_std::if_std; if_std! { use $crate::tracing; @@ -1011,7 +1012,7 @@ macro_rules! decl_module { { $( $other_where_bounds:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OnInitialize<$trait_instance::BlockNumber> + $crate::traits::OnInitialize<$trait_instance::BlockNumber> for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* {} }; @@ -1019,14 +1020,13 @@ macro_rules! decl_module { (@impl_on_runtime_upgrade $module:ident<$trait_instance:ident: $trait_name:ident$(, $instance:ident: $instantiable:path)?>; { $( $other_where_bounds:tt )* } - #[weight = $weight:expr] - fn on_runtime_upgrade() { $( $impl:tt )* } + fn on_runtime_upgrade() -> $return:ty { $( $impl:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OnRuntimeUpgrade + $crate::traits::OnRuntimeUpgrade for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* { - fn on_runtime_upgrade() { + fn on_runtime_upgrade() -> $return { use $crate::sp_std::if_std; if_std! { use $crate::tracing; @@ -1043,7 +1043,7 @@ macro_rules! decl_module { { $( $other_where_bounds:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OnRuntimeUpgrade + $crate::traits::OnRuntimeUpgrade for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* {} }; @@ -1052,11 +1052,10 @@ macro_rules! decl_module { (@impl_on_finalize $module:ident<$trait_instance:ident: $trait_name:ident$(, $instance:ident: $instantiable:path)?>; { $( $other_where_bounds:tt )* } - #[weight = $weight:expr] fn on_finalize() { $( $impl:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OnFinalize<$trait_instance::BlockNumber> + $crate::traits::OnFinalize<$trait_instance::BlockNumber> for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* { fn on_finalize(_block_number_not_used: $trait_instance::BlockNumber) { @@ -1074,11 +1073,10 @@ macro_rules! decl_module { (@impl_on_finalize $module:ident<$trait_instance:ident: $trait_name:ident$(, $instance:ident: $instantiable:path)?>; { $( $other_where_bounds:tt )* } - #[weight = $weight:expr] fn on_finalize($param:ident : $param_ty:ty) { $( $impl:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OnFinalize<$trait_instance::BlockNumber> + $crate::traits::OnFinalize<$trait_instance::BlockNumber> for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* { fn on_finalize($param: $param_ty) { @@ -1098,57 +1096,19 @@ macro_rules! decl_module { { $( $other_where_bounds:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OnFinalize<$trait_instance::BlockNumber> + $crate::traits::OnFinalize<$trait_instance::BlockNumber> for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* { } }; - (@impl_block_hooks_weight - $module:ident<$trait_instance:ident: $trait_name:ident$(, $instance:ident: $instantiable:path)?>; - { $( $other_where_bounds:tt )* } - @runtime_upgrade $( - #[weight = $weight_runtime_update:expr] - fn on_runtime_upgrade($( $param_runtime_upgrade:ident : $param_ty_runtime_upgrade:ty )*) { $( $impl_runtime_upgrade:tt )* } - )? - @init $( - #[weight = $weight_initialize:expr] - fn on_initialize($( $param_initialize:ident : $param_ty_initialize:ty )*) { $( $impl_initialize:tt )* } - )? - @fin $( - #[weight = $weight_finalize:expr] - fn on_finalize($( $param_finalize:ident : $param_ty_finalize:ty )*) { $( $impl_finalize:tt )* } - )? - ) => { - impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::dispatch::WeighBlock<$trait_instance::BlockNumber> for $module<$trait_instance$(, $instance)?> where - $( $other_where_bounds )* - { - $( - fn on_runtime_upgrade() -> $crate::dispatch::Weight { - >::weigh_data(&$weight_initialize, ()) - } - )? - $( - fn on_initialize(n: $trait_instance::BlockNumber) -> $crate::dispatch::Weight { - >::weigh_data(&$weight_initialize, n) - } - )? - $( - fn on_finalize(n: $trait_instance::BlockNumber) -> $crate::dispatch::Weight { - >::weigh_data(&$weight_finalize, n) - } - )? - } - }; - (@impl_offchain $module:ident<$trait_instance:ident: $trait_name:ident$(, $instance:ident: $instantiable:path)?>; { $( $other_where_bounds:tt )* } fn offchain_worker() { $( $impl:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OffchainWorker<$trait_instance::BlockNumber> + $crate::traits::OffchainWorker<$trait_instance::BlockNumber> for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* { fn offchain_worker(_block_number_not_used: $trait_instance::BlockNumber) { $( $impl )* } @@ -1161,7 +1121,7 @@ macro_rules! decl_module { fn offchain_worker($param:ident : $param_ty:ty) { $( $impl:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OffchainWorker<$trait_instance::BlockNumber> + $crate::traits::OffchainWorker<$trait_instance::BlockNumber> for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* { fn offchain_worker($param: $param_ty) { $( $impl )* } @@ -1173,7 +1133,7 @@ macro_rules! decl_module { { $( $other_where_bounds:tt )* } ) => { impl<$trait_instance: $trait_name$(, $instance: $instantiable)?> - $crate::sp_runtime::traits::OffchainWorker<$trait_instance::BlockNumber> + $crate::traits::OffchainWorker<$trait_instance::BlockNumber> for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )* {} }; @@ -1402,15 +1362,6 @@ macro_rules! decl_module { $( $on_finalize )* } - $crate::decl_module! { - @impl_block_hooks_weight - $mod_type<$trait_instance: $trait_name $(, $instance: $instantiable)?>; - { $( $other_where_bounds )* } - @runtime_upgrade $( $on_runtime_upgrade )* - @init $( $on_initialize )* - @fin $( $on_finalize )* - } - $crate::decl_module! { @impl_offchain $mod_type<$trait_instance: $trait_name $(, $instance: $instantiable)?>; @@ -1483,9 +1434,9 @@ macro_rules! decl_module { &$weight, ($( $param_name, )*) ); - $crate::dispatch::DispatchInfo { - weight, - class, + $crate::dispatch::DispatchInfo { + weight, + class, pays_fee, } }, @@ -2076,9 +2027,10 @@ macro_rules! __check_reserved_fn_name { #[allow(dead_code)] mod tests { use super::*; - use crate::sp_runtime::traits::{OnInitialize, OnFinalize, OnRuntimeUpgrade}; use crate::weights::{DispatchInfo, DispatchClass}; - use crate::traits::{CallMetadata, GetCallMetadata, GetCallName}; + use crate::traits::{ + CallMetadata, GetCallMetadata, GetCallName, OnInitialize, OnFinalize, OnRuntimeUpgrade + }; pub trait Trait: system::Trait + Sized where Self::AccountId: From { type Origin; @@ -2098,35 +2050,34 @@ mod tests { } } - struct BlockWeight; - impl> WeighData for BlockWeight { - fn weigh_data(&self, target: BlockNumber) -> Weight { - let target: u32 = target.into(); - if target % 2 == 0 { 10 } else { 0 } - } - } - decl_module! { pub struct Module for enum Call where origin: T::Origin, T::AccountId: From { /// Hi, this is a comment. + #[weight = SimpleDispatchInfo::default()] fn aux_0(_origin) -> DispatchResult { unreachable!() } + + #[weight = SimpleDispatchInfo::default()] fn aux_1(_origin, #[compact] _data: u32,) -> DispatchResult { unreachable!() } + + #[weight = SimpleDispatchInfo::default()] fn aux_2(_origin, _data: i32, _data2: String) -> DispatchResult { unreachable!() } + #[weight = SimpleDispatchInfo::FixedNormal(3)] fn aux_3(_origin) -> DispatchResult { unreachable!() } + + #[weight = SimpleDispatchInfo::default()] fn aux_4(_origin, _data: i32) -> DispatchResult { unreachable!() } - fn aux_5(_origin, _data: i32, #[compact] _data2: u32,) -> DispatchResult { unreachable!() } - #[weight = SimpleDispatchInfo::FixedNormal(7)] - fn on_initialize(n: T::BlockNumber,) { if n.into() == 42 { panic!("on_initialize") } } - #[weight = BlockWeight] - fn on_finalize(n: T::BlockNumber) { if n.into() == 42 { panic!("on_finalize") } } - #[weight = SimpleDispatchInfo::FixedOperational(69)] - fn on_runtime_upgrade() { } - fn offchain_worker() {} + #[weight = SimpleDispatchInfo::default()] + fn aux_5(_origin, _data: i32, #[compact] _data2: u32,) -> DispatchResult { unreachable!() } #[weight = SimpleDispatchInfo::FixedOperational(5)] - fn operational(_origin,) { unreachable!() } + fn operational(_origin) { unreachable!() } + + fn on_initialize(n: T::BlockNumber,) -> Weight { if n.into() == 42 { panic!("on_initialize") } 7 } + fn on_finalize(n: T::BlockNumber,) { if n.into() == 42 { panic!("on_finalize") } } + fn on_runtime_upgrade() -> Weight { 10 } + fn offchain_worker() {} } } @@ -2254,10 +2205,15 @@ mod tests { #[test] #[should_panic(expected = "on_initialize")] - fn on_initialize_should_work() { + fn on_initialize_should_work_1() { as OnInitialize>::on_initialize(42); } + #[test] + fn on_initialize_should_work_2() { + assert_eq!( as OnInitialize>::on_initialize(10), 7); + } + #[test] #[should_panic(expected = "on_finalize")] fn on_finalize_should_work() { @@ -2266,7 +2222,7 @@ mod tests { #[test] fn on_runtime_upgrade_should_work() { - as OnRuntimeUpgrade>::on_runtime_upgrade(); + assert_eq!( as OnRuntimeUpgrade>::on_runtime_upgrade(), 10); } #[test] @@ -2288,18 +2244,6 @@ mod tests { ); } - #[test] - fn weight_for_block_hooks() { - // independent of block number - assert_eq!(>::on_initialize(0), 7); - assert_eq!(>::on_initialize(10), 7); - assert_eq!(>::on_initialize(100), 7); - - // dependent - assert_eq!(>::on_finalize(2), 10); - assert_eq!(>::on_finalize(3), 0); - } - #[test] fn call_name() { let name = Call::::aux_3().get_call_name(); diff --git a/frame/support/src/error.rs b/frame/support/src/error.rs index 3b105e979d09affe2efe0a2a5b81cf99d474201f..f619250726deda5caea1f76894d1a75793458f01 100644 --- a/frame/support/src/error.rs +++ b/frame/support/src/error.rs @@ -35,6 +35,7 @@ pub use frame_metadata::{ModuleErrorMetadata, ErrorMetadata, DecodeDifferent}; /// /// ``` /// # use frame_support::{decl_error, decl_module}; +/// # use frame_support::weights::SimpleDispatchInfo; /// decl_error! { /// /// Errors that can occur in my module. /// pub enum MyError for Module { @@ -54,6 +55,7 @@ pub use frame_metadata::{ModuleErrorMetadata, ErrorMetadata, DecodeDifferent}; /// pub struct Module for enum Call where origin: T::Origin { /// type Error = MyError; /// +/// #[weight = SimpleDispatchInfo::default()] /// fn do_something(origin) -> frame_support::dispatch::DispatchResult { /// Err(MyError::::YouAreNotCoolEnough.into()) /// } diff --git a/frame/support/src/event.rs b/frame/support/src/event.rs index 8b7de01159df3e1417dfdb82c08f684237442f13..1184b379f44469d72278dd1562223ef51631c6d4 100644 --- a/frame/support/src/event.rs +++ b/frame/support/src/event.rs @@ -288,6 +288,7 @@ macro_rules! __decl_generic_event { } impl<$( $generic_param ),* $(, $instance)?> RawEvent<$( $generic_param ),* $(, $instance)?> { #[allow(dead_code)] + #[doc(hidden)] pub fn metadata() -> &'static [$crate::event::EventMetadata] { $crate::__events_to_metadata!(; $( $events )* ) } diff --git a/frame/support/src/hash.rs b/frame/support/src/hash.rs index f8d6409060f1910700afefb746739c373d8c2b49..693e929a309e366b0605805d1721429904d04806 100644 --- a/frame/support/src/hash.rs +++ b/frame/support/src/hash.rs @@ -92,6 +92,10 @@ impl StorageHasher for Twox64Concat { } impl ReversibleStorageHasher for Twox64Concat { fn reverse(x: &[u8]) -> &[u8] { + if x.len() < 8 { + crate::debug::error!("Invalid reverse: hash length too short"); + return &[] + } &x[8..] } } @@ -110,6 +114,10 @@ impl StorageHasher for Blake2_128Concat { } impl ReversibleStorageHasher for Blake2_128Concat { fn reverse(x: &[u8]) -> &[u8] { + if x.len() < 16 { + crate::debug::error!("Invalid reverse: hash length too short"); + return &[] + } &x[16..] } } diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 8fe32cbda999935e84bbd043bc828264e54abe1e..f242efecc401e098cfbc8b918eba5e7222a782d3 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -72,7 +72,7 @@ pub use self::hash::{ }; pub use self::storage::{ StorageValue, StorageMap, StorageDoubleMap, StoragePrefixedMap, IterableStorageMap, - IterableStorageDoubleMap, + IterableStorageDoubleMap, migration }; pub use self::dispatch::{Parameter, Callable, IsSubType}; pub use sp_runtime::{self, ConsensusEngineId, print, traits::Printable}; diff --git a/frame/support/src/metadata.rs b/frame/support/src/metadata.rs index 46662e53548a51d8cda0def166d2126a7c0c2216..d9c8136d3c442a99a57e74fe6851193818ac0bb5 100644 --- a/frame/support/src/metadata.rs +++ b/frame/support/src/metadata.rs @@ -336,6 +336,7 @@ mod tests { mod event_module { use crate::dispatch::DispatchResult; + use crate::weights::SimpleDispatchInfo; pub trait Trait: super::system::Trait { type Balance; @@ -353,6 +354,7 @@ mod tests { pub struct Module for enum Call where origin: T::Origin { type Error = Error; + #[weight = SimpleDispatchInfo::default()] fn aux_0(_origin) -> DispatchResult { unreachable!() } } } diff --git a/frame/support/src/storage/migration.rs b/frame/support/src/storage/migration.rs index df9758ba69839d4d103acd477601d02c39828369..8e6beefa8886e702fbd0536bfaa75d86d5f505b9 100644 --- a/frame/support/src/storage/migration.rs +++ b/frame/support/src/storage/migration.rs @@ -19,6 +19,7 @@ use sp_std::prelude::*; use codec::{Encode, Decode}; use crate::{StorageHasher, Twox128}; +use crate::hash::ReversibleStorageHasher; /// Utility to iterate through raw items in storage. pub struct StorageIterator { @@ -78,6 +79,72 @@ impl Iterator for StorageIterator { } } +/// Utility to iterate through raw items in storage. +pub struct StorageKeyIterator { + prefix: Vec, + previous_key: Vec, + drain: bool, + _phantom: ::sp_std::marker::PhantomData<(K, T, H)>, +} + +impl StorageKeyIterator { + /// Construct iterator to iterate over map items in `module` for the map called `item`. + pub fn new(module: &[u8], item: &[u8]) -> Self { + Self::with_suffix(module, item, &[][..]) + } + + /// Construct iterator to iterate over map items in `module` for the map called `item`. + pub fn with_suffix(module: &[u8], item: &[u8], suffix: &[u8]) -> Self { + let mut prefix = Vec::new(); + prefix.extend_from_slice(&Twox128::hash(module)); + prefix.extend_from_slice(&Twox128::hash(item)); + prefix.extend_from_slice(suffix); + let previous_key = prefix.clone(); + Self { prefix, previous_key, drain: false, _phantom: Default::default() } + } + + /// Mutate this iterator into a draining iterator; items iterated are removed from storage. + pub fn drain(mut self) -> Self { + self.drain = true; + self + } +} + +impl Iterator + for StorageKeyIterator +{ + type Item = (K, T); + + fn next(&mut self) -> Option<(K, T)> { + loop { + let maybe_next = sp_io::storage::next_key(&self.previous_key) + .filter(|n| n.starts_with(&self.prefix)); + break match maybe_next { + Some(next) => { + self.previous_key = next.clone(); + let mut key_material = H::reverse(&next[self.prefix.len()..]); + match K::decode(&mut key_material) { + Ok(key) => { + let maybe_value = frame_support::storage::unhashed::get::(&next); + match maybe_value { + Some(value) => { + if self.drain { + frame_support::storage::unhashed::kill(&next); + } + Some((key, value)) + } + None => continue, + } + } + Err(_) => continue, + } + } + None => None, + } + } + } +} + /// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`. pub fn have_storage_value(module: &[u8], item: &[u8], hash: &[u8]) -> bool { get_storage_value::<()>(module, item, hash).is_some() @@ -109,3 +176,21 @@ pub fn put_storage_value(module: &[u8], item: &[u8], hash: &[u8], val key[32..].copy_from_slice(hash); frame_support::storage::unhashed::put(&key, &value); } + +/// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`. +pub fn remove_storage_prefix(module: &[u8], item: &[u8], hash: &[u8]) { + let mut key = vec![0u8; 32 + hash.len()]; + key[0..16].copy_from_slice(&Twox128::hash(module)); + key[16..32].copy_from_slice(&Twox128::hash(item)); + key[32..].copy_from_slice(hash); + frame_support::storage::unhashed::kill_prefix(&key) +} + +/// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`. +pub fn take_storage_item( + module: &[u8], + item: &[u8], + key: K, +) -> Option { + take_storage_value(module, item, key.using_encoded(H::hash).as_ref()) +} diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index d36656a740344df6ca98104a4482ffd810ff503d..77e4c679f4e68e4bf0d914f75ca8dc7b22ea122f 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -24,17 +24,11 @@ use sp_core::u32_trait::Value as U32; use sp_runtime::{ RuntimeDebug, ConsensusEngineId, DispatchResult, DispatchError, - traits::{MaybeSerializeDeserialize, AtLeast32Bit, Saturating, TrailingZeroInput}, + traits::{MaybeSerializeDeserialize, AtLeast32Bit, Saturating, TrailingZeroInput, Bounded}, }; use crate::dispatch::Parameter; use crate::storage::StorageMap; - -/// Migrate a given account. -#[impl_trait_for_tuples::impl_for_tuples(30)] -pub trait MigrateAccount { - /// Migrate the `account`. - fn migrate_account(account: &A); -} +use impl_trait_for_tuples::impl_for_tuples; /// An abstraction of a value stored within storage, but possibly as part of a larger composite /// item. @@ -93,7 +87,7 @@ impl< Created: Happened, Removed: Happened, K: FullCodec, - T: FullCodec + T: FullCodec, > StoredMap for StorageMapShim { fn get(k: &K) -> T { S::get(k) } fn is_explicit(k: &K) -> bool { S::contains_key(k) } @@ -144,6 +138,35 @@ impl< } } +/// Something that can estimate at which block the next session rotation will happen. This should +/// be the same logical unit that dictates `ShouldEndSession` to the session module. No Assumptions +/// are made about the scheduling of the sessions. +pub trait EstimateNextSessionRotation { + /// Return the block number at which the next session rotation is estimated to happen. + /// + /// None should be returned if the estimation fails to come to an answer + fn estimate_next_session_rotation(now: BlockNumber) -> Option; +} + +impl EstimateNextSessionRotation for () { + fn estimate_next_session_rotation(_: BlockNumber) -> Option { + Default::default() + } +} + +/// Something that can estimate at which block the next `new_session` will be triggered. This must +/// always be implemented by the session module. +pub trait EstimateNextNewSession { + /// Return the block number at which the next new session is estimated to happen. + fn estimate_next_new_session(now: BlockNumber) -> Option; +} + +impl EstimateNextNewSession for () { + fn estimate_next_new_session(_: BlockNumber) -> Option { + Default::default() + } +} + /// Anything that can have a `::len()` method. pub trait Len { /// Return the length of data type. @@ -178,6 +201,13 @@ pub trait Contains { /// Get the number of items in the set. fn count() -> usize { Self::sorted_members().len() } + + /// Add an item that would satisfy `contains`. It does not make sure any other + /// state is correctly maintained or generated. + /// + /// **Should be used for benchmarking only!!!** + #[cfg(feature = "runtime-benchmarks")] + fn add(t: &T); } /// Determiner to say whether a given account is unused. @@ -193,14 +223,14 @@ impl IsDeadAccount for () { } /// Handler for when a new account has been created. -#[impl_trait_for_tuples::impl_for_tuples(30)] +#[impl_for_tuples(30)] 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)] +#[impl_for_tuples(30)] pub trait OnKilledAccount { /// The account with the given id was reaped. fn on_killed_account(who: &AccountId); @@ -857,6 +887,12 @@ pub trait Time { fn now() -> Self::Moment; } +/// Trait to deal with unix time. +pub trait UnixTime { + /// Return duration since `SystemTime::UNIX_EPOCH`. + fn now() -> core::time::Duration; +} + impl WithdrawReasons { /// Choose all variants except for `one`. /// @@ -1042,3 +1078,91 @@ pub trait GetCallMetadata { /// Return a [`CallMetadata`], containing function and pallet name of the Call. fn get_call_metadata(&self) -> CallMetadata; } + +/// The block finalization trait. Implementing this lets you express what should happen +/// for your module when the block is ending. +#[impl_for_tuples(30)] +pub trait OnFinalize { + /// The block is being finalized. Implement to have something happen. + fn on_finalize(_n: BlockNumber) {} +} + +/// The block initialization trait. Implementing this lets you express what should happen +/// for your module when the block is beginning (right before the first extrinsic is executed). +pub trait OnInitialize { + /// 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 } +} + +#[impl_for_tuples(30)] +impl OnInitialize for Tuple { + fn on_initialize(_n: BlockNumber) -> crate::weights::Weight { + let mut weight = 0; + for_tuples!( #( weight = weight.saturating_add(Tuple::on_initialize(_n.clone())); )* ); + weight + } +} + +/// The runtime upgrade trait. Implementing this lets you express what should happen +/// when the runtime upgrades, and changes may need to occur to your module. +pub trait OnRuntimeUpgrade { + /// Perform a module upgrade. + /// + /// Return the non-negotiable weight consumed for runtime upgrade. + fn on_runtime_upgrade() -> crate::weights::Weight { 0 } +} + +#[impl_for_tuples(30)] +impl OnRuntimeUpgrade for Tuple { + fn on_runtime_upgrade() -> crate::weights::Weight { + let mut weight = 0; + for_tuples!( #( weight = weight.saturating_add(Tuple::on_runtime_upgrade()); )* ); + weight + } +} + +/// Off-chain computation trait. +/// +/// Implementing this trait on a module allows you to perform long-running tasks +/// that make (by default) validators generate transactions that feed results +/// of those long-running computations back on chain. +/// +/// NOTE: This function runs off-chain, so it can access the block state, +/// 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_for_tuples(30)] +pub trait OffchainWorker { + /// This function is being called after every block import (when fully synced). + /// + /// Implement this and use any of the `Offchain` `sp_io` set of APIs + /// to perform off-chain computations, calls and submit transactions + /// with results to trigger any on-chain changes. + /// Any state alterations are lost and are not persisted. + fn offchain_worker(_n: BlockNumber) {} +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn on_initialize_and_on_runtime_upgrade_weight_merge_works() { + struct Test; + impl OnInitialize for Test { + fn on_initialize(_n: u8) -> crate::weights::Weight { + 10 + } + } + impl OnRuntimeUpgrade for Test { + fn on_runtime_upgrade() -> crate::weights::Weight { + 20 + } + } + + assert_eq!(<(Test, Test)>::on_initialize(0), 20); + assert_eq!(<(Test, Test)>::on_runtime_upgrade(), 40); + } +} diff --git a/frame/support/src/unsigned.rs b/frame/support/src/unsigned.rs index 4289e4e474f53971f29f75208d9cf865b6e173a2..3bc6f692affc20426f196792666fe1c0413c146f 100644 --- a/frame/support/src/unsigned.rs +++ b/frame/support/src/unsigned.rs @@ -18,7 +18,7 @@ pub use crate::sp_runtime::traits::ValidateUnsigned; #[doc(hidden)] pub use crate::sp_runtime::transaction_validity::{ - TransactionValidity, UnknownTransaction, TransactionValidityError, + TransactionValidity, UnknownTransaction, TransactionValidityError, TransactionSource, }; @@ -34,7 +34,8 @@ pub use crate::sp_runtime::transaction_validity::{ /// # impl frame_support::unsigned::ValidateUnsigned for Module { /// # type Call = Call; /// # -/// # fn validate_unsigned(call: &Self::Call) -> frame_support::unsigned::TransactionValidity { +/// # fn validate_unsigned(_source: frame_support::unsigned::TransactionSource, _call: &Self::Call) +/// -> frame_support::unsigned::TransactionValidity { /// # unimplemented!(); /// # } /// # } @@ -78,10 +79,14 @@ macro_rules! impl_outer_validate_unsigned { } } - fn validate_unsigned(call: &Self::Call) -> $crate::unsigned::TransactionValidity { + fn validate_unsigned( + #[allow(unused_variables)] + source: $crate::unsigned::TransactionSource, + call: &Self::Call, + ) -> $crate::unsigned::TransactionValidity { #[allow(unreachable_patterns)] match call { - $( Call::$module(inner_call) => $module::validate_unsigned(inner_call), )* + $( Call::$module(inner_call) => $module::validate_unsigned(source, inner_call), )* _ => $crate::unsigned::UnknownTransaction::NoUnsignedValidator.into(), } } @@ -110,7 +115,10 @@ mod test_partial_and_full_call { impl super::super::ValidateUnsigned for Module { type Call = Call; - fn validate_unsigned(_call: &Self::Call) -> super::super::TransactionValidity { + fn validate_unsigned( + _source: super::super::TransactionSource, + _call: &Self::Call + ) -> super::super::TransactionValidity { unimplemented!(); } } diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index 8926ed949302b304935a5df7431eae09a886eb57..7e8174ca7b145f47445b5ab62ab0e3ad87244988 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -37,9 +37,8 @@ #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; -use impl_trait_for_tuples::impl_for_tuples; use codec::{Encode, Decode}; -use sp_arithmetic::traits::{Bounded, Zero}; +use sp_arithmetic::traits::Bounded; use sp_runtime::{ RuntimeDebug, traits::SignedExtension, @@ -67,17 +66,6 @@ pub trait ClassifyDispatch { fn classify_dispatch(&self, target: T) -> DispatchClass; } -/// Means of determining the weight of a block's life cycle hooks: `on_initialize`, `on_finalize`, -/// `on_runtime_upgrade`, and such. -pub trait WeighBlock { - /// Return the weight of the block's on_runtime_upgrade hook. - fn on_runtime_upgrade() -> Weight { Zero::zero() } - /// Return the weight of the block's on_initialize hook. - fn on_initialize(_: BlockNumber) -> Weight { Zero::zero() } - /// Return the weight of the block's on_finalize hook. - fn on_finalize(_: BlockNumber) -> Weight { Zero::zero() } -} - /// Indicates if dispatch function should pay fees or not. /// If set to false, the block resource limits are applied, yet no fee is deducted. pub trait PaysFee { @@ -86,34 +74,6 @@ pub trait PaysFee { } } -/// Maybe I can do something to remove the duplicate code here. -#[impl_for_tuples(30)] -impl WeighBlock for SingleModule { - fn on_runtime_upgrade() -> Weight { - let mut accumulated_weight: Weight = Zero::zero(); - for_tuples!( - #( accumulated_weight = accumulated_weight.saturating_add(SingleModule::on_runtime_upgrade()); )* - ); - accumulated_weight - } - - fn on_initialize(n: BlockNumber) -> Weight { - let mut accumulated_weight: Weight = Zero::zero(); - for_tuples!( - #( accumulated_weight = accumulated_weight.saturating_add(SingleModule::on_initialize(n)); )* - ); - accumulated_weight - } - - fn on_finalize(n: BlockNumber) -> Weight { - let mut accumulated_weight: Weight = Zero::zero(); - for_tuples!( - #( accumulated_weight = accumulated_weight.saturating_add(SingleModule::on_finalize(n)); )* - ); - accumulated_weight - } -} - /// A generalized group of dispatch types. This is only distinguishing normal, user-triggered transactions /// (`Normal`) and anything beyond which serves a higher purpose to the system (`Operational`). #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] diff --git a/frame/support/test/Cargo.toml b/frame/support/test/Cargo.toml index 0a5595914b07b28ea416767753349ec253293dae..773523579b0b4ce8d580e5e80aad796591947fdf 100644 --- a/frame/support/test/Cargo.toml +++ b/frame/support/test/Cargo.toml @@ -10,13 +10,13 @@ repository = "https://github.com/paritytech/substrate/" [dependencies] serde = { version = "1.0.101", default-features = false, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-io ={ path = "../../../primitives/io", default-features = false , version = "2.0.0-alpha.2"} -sp-state-machine = { version = "0.8.0-alpha.2", optional = true, path = "../../../primitives/state-machine" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../" } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/inherents" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/runtime" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/core" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-io ={ path = "../../../primitives/io", default-features = false , version = "2.0.0-alpha.5"} +sp-state-machine = { version = "0.8.0-alpha.5", optional = true, path = "../../../primitives/state-machine" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../" } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/inherents" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/runtime" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../../primitives/core" } trybuild = "1.0.17" pretty_assertions = "0.6.1" @@ -32,3 +32,6 @@ std = [ "sp-runtime/std", "sp-state-machine", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/support/test/tests/decl_error.rs b/frame/support/test/tests/decl_error.rs index 39d7dbdb9647857ce1d054d53acac90fda42d085..4191e79f2417ca59d118e0265094fa4e9a5b8782 100644 --- a/frame/support/test/tests/decl_error.rs +++ b/frame/support/test/tests/decl_error.rs @@ -32,6 +32,7 @@ mod module1 { pub struct Module, I: Instance = DefaultInstance> for enum Call where origin: ::Origin { + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { Err(Error::::Something.into()) } @@ -58,6 +59,7 @@ mod module2 { pub struct Module for enum Call where origin: ::Origin { + #[weight = frame_support::weights::SimpleDispatchInfo::default()] pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { Err(Error::::Something.into()) } diff --git a/frame/support/test/tests/instance.rs b/frame/support/test/tests/instance.rs index 92281ef1311a79aa4908e13f024042c2a2e07bc8..ea5d32fea3b222b754331aff047d65782b671911 100644 --- a/frame/support/test/tests/instance.rs +++ b/frame/support/test/tests/instance.rs @@ -55,6 +55,7 @@ mod module1 { fn deposit_event() = default; + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn one(origin) { system::ensure_root(origin)?; Self::deposit_event(RawEvent::AnotherVariant(3)); diff --git a/frame/support/test/tests/reserved_keyword/on_initialize.rs b/frame/support/test/tests/reserved_keyword/on_initialize.rs index 84feb2d93f36cfc2212f44a7fa646d42501355b8..8eacc836c48685c1516ce129780c71f0ee636ea1 100644 --- a/frame/support/test/tests/reserved_keyword/on_initialize.rs +++ b/frame/support/test/tests/reserved_keyword/on_initialize.rs @@ -19,6 +19,7 @@ macro_rules! reserved { frame_support::decl_module! { pub struct Module for enum Call where origin: T::Origin { + #[weight = frame_support::weights::SimpleDispatchInfo::default()] fn $reserved(_origin) -> dispatch::DispatchResult { unreachable!() } } } diff --git a/frame/support/test/tests/reserved_keyword/on_initialize.stderr b/frame/support/test/tests/reserved_keyword/on_initialize.stderr index d20a6e11451e7d52f4ded9a810ddc81f0d58bbb8..e899ef5d789426c620a9c92f7190a2ac5fdabd4f 100644 --- a/frame/support/test/tests/reserved_keyword/on_initialize.stderr +++ b/frame/support/test/tests/reserved_keyword/on_initialize.stderr @@ -1,39 +1,39 @@ 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:30:1 + --> $DIR/on_initialize.rs:31:1 | -30 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); +31 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-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:30:1 + --> $DIR/on_initialize.rs:31:1 | -30 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); +31 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-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:30:1 + --> $DIR/on_initialize.rs:31:1 | -30 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); +31 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-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:30:1 + --> $DIR/on_initialize.rs:31:1 | -30 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); +31 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) -error: `deposit_event` function is reserved and must follow the syntax: `$vis:vis fn deposit_event() = default;` - --> $DIR/on_initialize.rs:30:1 +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:31:1 | -30 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); +31 | reserved!(on_finalize on_initialize on_runtime_upgrade offchain_worker deposit_event); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) diff --git a/frame/system/Cargo.toml b/frame/system/Cargo.toml index 4e350be1a90c78b8a18c4da1a77f6d9e4c54f905..78288cff917e233306fe0bee01f5a04d96263f16 100644 --- a/frame/system/Cargo.toml +++ b/frame/system/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-system" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,18 +10,18 @@ description = "FRAME system module" [dependencies] serde = { version = "1.0.101", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { path = "../../primitives/io", default-features = false, version = "2.0.0-alpha.2" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-version = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/version" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { path = "../../primitives/io", default-features = false, version = "2.0.0-alpha.5"} +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-version = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/version" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } impl-trait-for-tuples = "0.1.3" [dev-dependencies] criterion = "0.2.11" -sp-externalities = { version = "0.8.0-alpha.2", path = "../../primitives/externalities" } +sp-externalities = { version = "0.8.0-alpha.5", path = "../../primitives/externalities" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../test-utils/runtime/client" } [features] @@ -36,8 +36,11 @@ std = [ "sp-runtime/std", "sp-version/std", ] -runtime-benchmarks = [] +runtime-benchmarks = ["sp-runtime/runtime-benchmarks"] [[bench]] name = "bench" harness = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/system/benches/bench.rs b/frame/system/benches/bench.rs index 03243bbfc411c20a05fb3ead71534bc25b075700..90a4ad1d34de80c7991a7a87fb3fb480e1945cd1 100644 --- a/frame/system/benches/bench.rs +++ b/frame/system/benches/bench.rs @@ -77,7 +77,7 @@ impl system::Trait for Runtime { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/system/rpc/runtime-api/Cargo.toml b/frame/system/rpc/runtime-api/Cargo.toml index 3bcc34698a56efcb82496a076bf9c507acd5d31d..9519427297425504a8fd1b9c769c5c2a6edd899e 100644 --- a/frame/system/rpc/runtime-api/Cargo.toml +++ b/frame/system/rpc/runtime-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-system-rpc-runtime-api" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,8 +9,8 @@ repository = "https://github.com/paritytech/substrate/" description = "Runtime API definition required by System RPC extensions." [dependencies] -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../../../primitives/api" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../../../primitives/api" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } [features] default = ["std"] @@ -18,3 +18,6 @@ std = [ "sp-api/std", "codec/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 29167d5b7e268c5cf004eb7917980c9db5cb4eb6..2d965b44f0777c8a76daa4c7c805acc60b69fc5d 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -68,13 +68,14 @@ //! ### Example - Get extrinsic count and parent hash for the current block //! //! ``` -//! use frame_support::{decl_module, dispatch}; +//! use frame_support::{decl_module, dispatch, weights::SimpleDispatchInfo}; //! use frame_system::{self as system, ensure_signed}; //! //! pub trait Trait: system::Trait {} //! //! decl_module! { //! pub struct Module for enum Call where origin: T::Origin { +//! #[weight = SimpleDispatchInfo::default()] //! pub fn system_module_example(origin) -> dispatch::DispatchResult { //! let _sender = ensure_signed(origin)?; //! let _extrinsic_count = >::extrinsic_count(); @@ -116,7 +117,7 @@ use frame_support::{ decl_module, decl_event, decl_storage, decl_error, storage, Parameter, ensure, debug, traits::{ Contains, Get, ModuleToIndex, OnNewAccount, OnKilledAccount, IsDeadAccount, Happened, - StoredMap, MigrateAccount, + StoredMap, }, weights::{Weight, DispatchInfo, DispatchClass, SimpleDispatchInfo, FunctionOf}, }; @@ -126,7 +127,6 @@ use codec::{Encode, Decode, FullCodec, EncodeLike}; use sp_io::TestExternalities; pub mod offchain; -mod migration; /// Compute the trie root of a list of extrinsics. pub fn extrinsics_root(extrinsics: &[E]) -> H::Output { @@ -222,9 +222,6 @@ pub trait Trait: 'static + Eq + Clone { /// /// All resources should be cleaned up associated with the given account. type OnKilledAccount: OnKilledAccount; - - /// Migrate an account. - type MigrateAccount: MigrateAccount; } pub type DigestOf = generic::Digest<::Hash>; @@ -239,8 +236,16 @@ pub type KeyValue = (Vec, Vec); pub enum Phase { /// Applying an extrinsic. ApplyExtrinsic(u32), - /// The end. + /// Finalizing the block. Finalization, + /// Initializing the block. + Initialization, +} + +impl Default for Phase { + fn default() -> Self { + Self::Initialization + } } /// Record of an event happening. @@ -361,7 +366,7 @@ decl_storage! { ExtrinsicData get(fn extrinsic_data): map hasher(twox_64_concat) u32 => Vec; /// The current block number being processed. Set by `execute_block`. - Number get(fn block_number) build(|_| 1.into()): T::BlockNumber; + Number get(fn block_number): T::BlockNumber; /// Hash of the previous block. ParentHash get(fn parent_hash) build(|_| hash69()): T::Hash; @@ -397,6 +402,9 @@ decl_storage! { /// Stores the `spec_version` and `spec_name` of when the last runtime upgrade happened. pub LastRuntimeUpgrade build(|_| Some(LastRuntimeUpgradeInfo::from(T::Version::get()))): Option; + + /// The execution phase of the block. + ExecutionPhase: Option; } add_extra_genesis { config(changes_trie_config): Option; @@ -460,15 +468,6 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin { type Error = Error; - fn on_runtime_upgrade() { - migration::migrate::(); - - // Remove the old `RuntimeUpgraded` storage entry. - let mut runtime_upgraded_key = sp_io::hashing::twox_128(b"System").to_vec(); - runtime_upgraded_key.extend(&sp_io::hashing::twox_128(b"RuntimeUpgraded")); - sp_io::storage::clear(&runtime_upgraded_key); - } - /// 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 decl_module macro). @@ -497,20 +496,7 @@ decl_module! { /// Set the new runtime code. #[weight = SimpleDispatchInfo::FixedOperational(200_000)] pub fn set_code(origin, code: Vec) { - ensure_root(origin)?; - - let current_version = T::Version::get(); - let new_version = sp_io::misc::runtime_version(&code) - .and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok()) - .ok_or_else(|| Error::::FailedToExtractRuntimeVersion)?; - - if new_version.spec_name != current_version.spec_name { - Err(Error::::InvalidSpecName)? - } - - if new_version.spec_version <= current_version.spec_version { - Err(Error::::SpecVersionNeedsToIncrease)? - } + Self::can_set_code(origin, &code)?; storage::unhashed::put_raw(well_known_keys::CODE, &code); Self::deposit_event(RawEvent::CodeUpdated); @@ -577,21 +563,6 @@ decl_module! { ensure!(account.data == T::AccountData::default(), Error::::NonDefaultComposite); Account::::remove(who); } - - #[weight = FunctionOf( - |(accounts,): (&Vec,)| accounts.len() as u32 * 10_000, - DispatchClass::Normal, - true, - )] - fn migrate_accounts(origin, accounts: Vec) { - let _ = ensure_signed(origin)?; - for a in &accounts { - if Account::::migrate_key_from_blake(a).is_some() { - // Inform other modules about the account. - T::MigrateAccount::migrate_account(a); - } - } - } } } @@ -607,12 +578,17 @@ impl< r => Err(O::from(r)), }) } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + O::from(RawOrigin::Root) + } } pub struct EnsureSigned(sp_std::marker::PhantomData); impl< O: Into, O>> + From>, - AccountId, + AccountId: Default, > EnsureOrigin for EnsureSigned { type Success = AccountId; fn try_origin(o: O) -> Result { @@ -621,13 +597,18 @@ impl< r => Err(O::from(r)), }) } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + O::from(RawOrigin::Signed(Default::default())) + } } pub struct EnsureSignedBy(sp_std::marker::PhantomData<(Who, AccountId)>); impl< O: Into, O>> + From>, Who: Contains, - AccountId: PartialEq + Clone + Ord, + AccountId: PartialEq + Clone + Ord + Default, > EnsureOrigin for EnsureSignedBy { type Success = AccountId; fn try_origin(o: O) -> Result { @@ -636,6 +617,13 @@ impl< r => Err(O::from(r)), }) } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + let caller: AccountId = Default::default(); + // Who::add(&caller); + O::from(RawOrigin::Signed(caller)) + } } pub struct EnsureNone(sp_std::marker::PhantomData); @@ -650,6 +638,11 @@ impl< r => Err(O::from(r)), }) } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + O::from(RawOrigin::None) + } } pub struct EnsureNever(sp_std::marker::PhantomData); @@ -658,6 +651,11 @@ impl EnsureOrigin for EnsureNever { fn try_origin(o: O) -> Result { Err(o) } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + unimplemented!() + } } /// Ensure that the origin `o` represents a signed extrinsic (i.e. transaction). @@ -751,8 +749,11 @@ impl Module { /// 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) { - let extrinsic_index = Self::extrinsic_index(); - let phase = extrinsic_index.map_or(Phase::Finalization, |c| Phase::ApplyExtrinsic(c)); + let block_number = Self::block_number(); + // Don't populate events on genesis. + if block_number.is_zero() { return } + + let phase = ExecutionPhase::get().unwrap_or_default(); let event = EventRecord { phase, event, @@ -784,10 +785,9 @@ impl Module { return; } - let block_no = Self::block_number(); for topic in topics { // The same applies here. - if >::append(topic, &[(block_no, event_idx)]).is_err() { + if >::append(topic, &[(block_number, event_idx)]).is_err() { return; } } @@ -844,6 +844,7 @@ impl Module { kind: InitKind, ) { // populate environment + ExecutionPhase::put(Phase::Initialization); storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &0u32); >::put(number); >::put(digest); @@ -860,6 +861,7 @@ impl Module { /// Remove temporary "environment" entries in storage. pub fn finalize() -> T::Header { + ExecutionPhase::kill(); ExtrinsicCount::kill(); AllExtrinsicsWeight::kill(); AllExtrinsicsLen::kill(); @@ -990,6 +992,7 @@ impl Module { let next_extrinsic_index = Self::extrinsic_index().unwrap_or_default() + 1u32; storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &next_extrinsic_index); + ExecutionPhase::put(Phase::ApplyExtrinsic(next_extrinsic_index)); } /// To be called immediately after `note_applied_extrinsic` of the last extrinsic of the block @@ -998,6 +1001,13 @@ impl Module { let extrinsic_index: u32 = storage::unhashed::take(well_known_keys::EXTRINSIC_INDEX) .unwrap_or_default(); ExtrinsicCount::put(extrinsic_index); + ExecutionPhase::put(Phase::Finalization); + } + + /// To be called immediately after finishing the initialization of the block + /// (e.g., called `on_initialize` for all modules). + pub fn note_finished_initialize() { + ExecutionPhase::put(Phase::ApplyExtrinsic(0)) } /// Remove all extrinsic data and save the extrinsics trie root. @@ -1037,6 +1047,32 @@ impl Module { Module::::on_killed_account(who.clone()); } } + + /// Determine whether or not it is possible to update the code. + /// + /// This function has no side effects and is idempotent, but is fairly + /// heavy. It is automatically called by `set_code`; in most cases, + /// a direct call to `set_code` is preferable. It is useful to call + /// `can_set_code` when it is desirable to perform the appropriate + /// runtime checks without actually changing the code yet. + pub fn can_set_code(origin: T::Origin, code: &[u8]) -> Result<(), sp_runtime::DispatchError> { + ensure_root(origin)?; + + let current_version = T::Version::get(); + let new_version = sp_io::misc::runtime_version(&code) + .and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok()) + .ok_or_else(|| Error::::FailedToExtractRuntimeVersion)?; + + if new_version.spec_name != current_version.spec_name { + Err(Error::::InvalidSpecName)? + } + + if new_version.spec_version <= current_version.spec_version { + Err(Error::::SpecVersionNeedsToIncrease)? + } + + Ok(()) + } } /// Event handler which calls on_created_account when it happens. @@ -1570,7 +1606,7 @@ mod tests { type Version = Version; type ModuleToIndex = (); type AccountData = u32; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = RecordKilled; } @@ -1660,6 +1696,8 @@ mod tests { &Default::default(), InitKind::Full, ); + System::deposit_event(32u16); + System::note_finished_initialize(); System::deposit_event(42u16); System::note_applied_extrinsic(&Ok(()), 0, Default::default()); System::note_applied_extrinsic(&Err(DispatchError::BadOrigin), 0, Default::default()); @@ -1669,6 +1707,7 @@ mod tests { assert_eq!( System::events(), vec![ + EventRecord { phase: Phase::Initialization, event: 32u16, topics: vec![] }, EventRecord { phase: Phase::ApplyExtrinsic(0), event: 42u16, topics: vec![] }, EventRecord { phase: Phase::ApplyExtrinsic(0), event: 100u16, topics: vec![] }, EventRecord { phase: Phase::ApplyExtrinsic(1), event: 101u16, topics: vec![] }, @@ -2005,6 +2044,7 @@ mod tests { let mut ext = new_test_ext(); ext.register_extension(sp_core::traits::CallInWasmExt::new(executor)); ext.execute_with(|| { + System::set_block_number(1); System::set_code( RawOrigin::Root.into(), substrate_test_runtime_client::runtime::WASM_BINARY.to_vec(), @@ -2012,7 +2052,7 @@ mod tests { assert_eq!( System::events(), - vec![EventRecord { phase: Phase::ApplyExtrinsic(0), event: 102u16, topics: vec![] }], + vec![EventRecord { phase: Phase::Initialization, event: 102u16, topics: vec![] }], ); }); } @@ -2032,4 +2072,18 @@ mod tests { ).unwrap(); }); } + + #[test] + fn events_not_emitted_during_genesis() { + new_test_ext().execute_with(|| { + // Block Number is zero at genesis + assert!(System::block_number().is_zero()); + System::on_created_account(Default::default()); + assert!(System::events().is_empty()); + // Events will be emitted starting on block 1 + System::set_block_number(1); + System::on_created_account(Default::default()); + assert!(System::events().len() == 1); + }); + } } diff --git a/frame/system/src/migration.rs b/frame/system/src/migration.rs deleted file mode 100644 index ece389f4f850342b23a60676db49e0b23d950494..0000000000000000000000000000000000000000 --- a/frame/system/src/migration.rs +++ /dev/null @@ -1,37 +0,0 @@ -use super::*; -use sp_runtime::traits::SaturatedConversion; - -pub fn migrate() { - // Number is current block - we obviously don't know that hash. - // Number - 1 is the parent block, who hash we record in this block, but then that's already - // with the new storage so we don't migrate it. - // Number - 2 is therefore the most recent block's hash that needs migrating. - if Number::::get() > One::one() { - sp_runtime::print("Migrating BlockHash..."); - BlockHash::::migrate_key_from_blake(T::BlockNumber::zero()); - let mut n = Number::::get() - One::one() - One::one(); - while !n.is_zero() { - sp_runtime::print(n.saturated_into::()); - if BlockHash::::migrate_key_from_blake(n).is_none() { - break; - } - n -= One::one(); - } - } - - sp_runtime::print("Migrating Accounts..."); - let mut count = 0u32; - if let Ok(accounts) = Vec::::decode(&mut &include_bytes!("accounts.scale")[..]) { - for a in &accounts { - if Account::::migrate_key_from_blake(a).is_some() { - // Inform other modules about the account. - T::MigrateAccount::migrate_account(a); - count += 1; - if count % 1000 == 0 { - sp_runtime::print(count); - } - } - } - } - sp_runtime::print(count); -} diff --git a/frame/system/src/offchain.rs b/frame/system/src/offchain.rs index 5abafe7655172c8b41d81a197d9510730df83f68..a3fe3e00ca4be879ed6074cc6b13f75e0fbae83f 100644 --- a/frame/system/src/offchain.rs +++ b/frame/system/src/offchain.rs @@ -95,7 +95,8 @@ impl Signer for TAnyAppPubl } /// Retrieves a public key type for given `SignAndSubmitTransaction`. -pub type PublicOf = < +pub type PublicOf = +< >::CreateTransaction as CreateTransaction>::Extrinsic> @@ -109,7 +110,7 @@ pub type PublicOf = < /// you should use. pub trait SignAndSubmitTransaction { /// Unchecked extrinsic type. - type Extrinsic: ExtrinsicT + codec::Encode; + type Extrinsic: ExtrinsicT + Encode; /// A runtime-specific type to produce signed data for the extrinsic. type CreateTransaction: CreateTransaction; @@ -156,7 +157,7 @@ pub trait SignAndSubmitTransaction { /// you should use. pub trait SubmitUnsignedTransaction { /// Unchecked extrinsic type. - type Extrinsic: ExtrinsicT + codec::Encode; + type Extrinsic: ExtrinsicT + Encode; /// Submit given call to the transaction pool as unsigned transaction. /// @@ -164,7 +165,8 @@ pub trait SubmitUnsignedTransaction { /// and `Err` if transaction was rejected from the pool. fn submit_unsigned(call: impl Into) -> Result<(), ()> { let xt = Self::Extrinsic::new(call.into(), None).ok_or(())?; - sp_io::offchain::submit_transaction(xt.encode()) + let encoded_xt = xt.encode(); + sp_io::offchain::submit_transaction(encoded_xt) } } @@ -291,7 +293,7 @@ impl SignAndSubmitTransaction for TransactionSubmitte T: crate::Trait, C: CreateTransaction, S: Signer<>::Public, >::Signature>, - E: ExtrinsicT + codec::Encode, + E: ExtrinsicT + Encode, { type Extrinsic = E; type CreateTransaction = C; @@ -301,7 +303,7 @@ impl SignAndSubmitTransaction for TransactionSubmitte /// A blanket implementation to use the same submitter for unsigned transactions as well. impl SubmitUnsignedTransaction for TransactionSubmitter where T: crate::Trait, - E: ExtrinsicT + codec::Encode, + E: ExtrinsicT + Encode, { type Extrinsic = E; } @@ -310,7 +312,7 @@ impl SubmitUnsignedTransaction for TransactionSubmitt impl SubmitSignedTransaction for TransactionSubmitter where T: crate::Trait, C: CreateTransaction, - E: ExtrinsicT + codec::Encode, + E: ExtrinsicT + Encode, S: Signer<>::Public, >::Signature>, // Make sure we can unwrap the app crypto key. S: RuntimeAppPublic + AppPublic + Into<::Generic>, diff --git a/frame/timestamp/Cargo.toml b/frame/timestamp/Cargo.toml index ff5b72de670e6cfd364c008e82ff3b0ec168be2d..eb7358e197a189dd33b55e985c1b9a65b86a72d8 100644 --- a/frame/timestamp/Cargo.toml +++ b/frame/timestamp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-timestamp" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,20 +12,20 @@ documentation = "https://docs.rs/pallet-timestamp" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io", optional = true } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/inherents" } -frame-benchmarking = { version = "2.0.0-alpha.2", default-features = false, path = "../benchmarking", optional = true } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -sp-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/timestamp" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io", optional = true } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" } +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +sp-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/timestamp" } impl-trait-for-tuples = "0.1.3" [dev-dependencies] -sp-io ={ version = "2.0.0-alpha.2", path = "../../primitives/io" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +sp-io ={ version = "2.0.0-alpha.5", path = "../../primitives/io" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } [features] default = ["std"] @@ -41,3 +41,6 @@ std = [ "sp-timestamp/std" ] runtime-benchmarks = ["frame-benchmarking", "sp-io"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/timestamp/src/benchmarking.rs b/frame/timestamp/src/benchmarking.rs index 5f69cdbe7e0463d0698b2e9fc12df536c51dc9e9..65b4dbf2b079ea23d4b2622b61847b99e41ce518 100644 --- a/frame/timestamp/src/benchmarking.rs +++ b/frame/timestamp/src/benchmarking.rs @@ -16,10 +16,10 @@ //! Timestamp pallet benchmarking. -use super::*; +#![cfg(feature = "runtime-benchmarks")] +use super::*; use sp_std::prelude::*; - use frame_system::RawOrigin; use frame_benchmarking::benchmarks; diff --git a/frame/timestamp/src/lib.rs b/frame/timestamp/src/lib.rs index 18e3be877b37b2523af0d3e273e3fde955b7b8f3..5d5180314eddf4221f48b72cdf14a8d1617a0168 100644 --- a/frame/timestamp/src/lib.rs +++ b/frame/timestamp/src/lib.rs @@ -69,6 +69,7 @@ //! //! decl_module! { //! pub struct Module for enum Call where origin: T::Origin { +//! #[weight = frame_support::weights::SimpleDispatchInfo::default()] //! pub fn get_time(origin) -> dispatch::DispatchResult { //! let _sender = ensure_signed(origin)?; //! let _now = >::get(); @@ -90,20 +91,21 @@ #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(feature = "runtime-benchmarks")] mod benchmarking; use sp_std::{result, cmp}; use sp_inherents::{ProvideInherent, InherentData, InherentIdentifier}; -use frame_support::{Parameter, decl_storage, decl_module}; -use frame_support::traits::{Time, Get}; +use frame_support::{ + Parameter, decl_storage, decl_module, debug, + traits::{Time, UnixTime, Get}, + weights::SimpleDispatchInfo, +}; use sp_runtime::{ RuntimeString, traits::{ AtLeast32Bit, Zero, SaturatedConversion, Scale } }; -use frame_support::weights::SimpleDispatchInfo; use frame_system::ensure_none; use sp_timestamp::{ InherentError, INHERENT_IDENTIFIER, InherentType, @@ -239,6 +241,25 @@ impl Time for Module { } } +/// Before the timestamp inherent is applied, it returns the time of previous block. +/// +/// On genesis the time returned is not valid. +impl UnixTime for Module { + fn now() -> core::time::Duration { + // now is duration since unix epoch in millisecond as documented in + // `sp_timestamp::InherentDataProvider`. + let now = Self::now(); + sp_std::if_std! { + if now == T::Moment::zero() { + debug::error!( + "`pallet_timestamp::UnixTime::now` is called at genesis, invalid value returned: 0" + ); + } + } + core::time::Duration::from_millis(now.saturated_into::()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -278,7 +299,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } parameter_types! { diff --git a/frame/transaction-payment/Cargo.toml b/frame/transaction-payment/Cargo.toml index 3a291dc51631288b43415039c4c1e831800c2d84..811c2885b1bdaa674247c7e9510531efa8fe0e95 100644 --- a/frame/transaction-payment/Cargo.toml +++ b/frame/transaction-payment/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-transaction-payment" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,17 +9,17 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet to manage transaction payments" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -pallet-transaction-payment-rpc-runtime-api = { version = "2.0.0-alpha.2", default-features = false, path = "./rpc/runtime-api" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +pallet-transaction-payment-rpc-runtime-api = { version = "2.0.0-alpha.5", default-features = false, path = "./rpc/runtime-api" } [dev-dependencies] -sp-io = { version = "2.0.0-alpha.2", path = "../../primitives/io" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } +sp-io = { version = "2.0.0-alpha.5", path = "../../primitives/io" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } [features] default = ["std"] @@ -31,3 +31,6 @@ std = [ "frame-system/std", "pallet-transaction-payment-rpc-runtime-api/std" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml index 309dfeedd5be420bb84615c1e0701ef74b5a58e4..cd74829b29b049fb18b29a661b00bd8763c3c725 100644 --- a/frame/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-transaction-payment-rpc" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,14 +9,17 @@ repository = "https://github.com/paritytech/substrate/" description = "RPC interface for the transaction payment module." [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0" } +codec = { package = "parity-scale-codec", version = "1.3.0" } jsonrpc-core = "14.0.3" -jsonrpc-core-client = "14.0.3" +jsonrpc-core-client = "14.0.5" jsonrpc-derive = "14.0.3" -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sp-rpc = { version = "2.0.0-alpha.2", path = "../../../primitives/rpc" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sp-rpc = { version = "2.0.0-alpha.5", path = "../../../primitives/rpc" } serde = { version = "1.0.101", features = ["derive"] } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-api = { version = "2.0.0-alpha.2", path = "../../../primitives/api" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -pallet-transaction-payment-rpc-runtime-api = { version = "2.0.0-alpha.2", path = "./runtime-api" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../primitives/api" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +pallet-transaction-payment-rpc-runtime-api = { version = "2.0.0-alpha.5", path = "./runtime-api" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/transaction-payment/rpc/runtime-api/Cargo.toml b/frame/transaction-payment/rpc/runtime-api/Cargo.toml index 96716769a39a89802544498acaabf52bab2c701c..447590111c7ee65ee098a2a508647c5fe7cefc57 100644 --- a/frame/transaction-payment/rpc/runtime-api/Cargo.toml +++ b/frame/transaction-payment/rpc/runtime-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-transaction-payment-rpc-runtime-api" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,11 +10,11 @@ description = "RPC runtime API for transaction payment FRAME pallet" [dependencies] serde = { version = "1.0.101", optional = true, features = ["derive"] } -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../../../primitives/api" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../../../support" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../../../primitives/api" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../../../support" } [dev-dependencies] serde_json = "1.0.41" @@ -29,3 +29,6 @@ std = [ "sp-runtime/std", "frame-support/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index 0306e74edb3d93e93e757b70e14ffa4c0c6771fb..1078927747b228cd7b7787aa2d911ab41818ef7d 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -106,11 +106,6 @@ impl Module { /// /// All dispatchables must be annotated with weight and will have some fee info. This function /// always returns. - // 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 `Extra`. - // Alternatively, we could actually execute the tx's per-dispatch and record the balance of the - // sender before and after the pipeline.. but this is way too much hassle for a very very little - // potential gain in the future. pub fn query_info( unchecked_extrinsic: Extrinsic, len: u32, @@ -119,6 +114,11 @@ impl Module { T: Send + Sync, BalanceOf: Send + Sync, { + // 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 + // `Extra`. Alternatively, we could actually execute the tx's per-dispatch and record the + // balance of the sender before and after the pipeline.. but this is way too much hassle for + // a very very little potential gain in the future. let dispatch_info = ::get_dispatch_info(&unchecked_extrinsic); let partial_fee = @@ -310,7 +310,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/frame/treasury/Cargo.toml b/frame/treasury/Cargo.toml index 99071e927635e0948bc47a065589e142396eab7e..6951c0eba9ae7bd566681e385ab8bf7ca9400718 100644 --- a/frame/treasury/Cargo.toml +++ b/frame/treasury/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-treasury" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,16 +10,18 @@ description = "FRAME pallet to manage treasury" [dependencies] serde = { version = "1.0.101", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -pallet-balances = { version = "2.0.0-alpha.2", default-features = false, path = "../balances" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +pallet-balances = { version = "2.0.0-alpha.5", default-features = false, path = "../balances" } + +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } [dev-dependencies] -sp-io ={ version = "2.0.0-alpha.2", path = "../../primitives/io" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } +sp-io ={ version = "2.0.0-alpha.5", path = "../../primitives/io" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } [features] default = ["std"] @@ -32,3 +34,10 @@ std = [ "frame-system/std", "pallet-balances/std", ] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", +] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/treasury/src/benchmarking.rs b/frame/treasury/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..925832e04cb1d8fa8b6fb8740454cf2ebdfee694 --- /dev/null +++ b/frame/treasury/src/benchmarking.rs @@ -0,0 +1,213 @@ +// Copyright 2020 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 . + +//! Treasury pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_system::RawOrigin; +use frame_benchmarking::{benchmarks, account}; +use frame_support::traits::OnInitialize; + +use crate::Module as Treasury; + +const SEED: u32 = 0; + +// Create the pre-requisite information needed to create a treasury `propose_spend`. +fn setup_proposal(u: u32) -> ( + T::AccountId, + BalanceOf, + ::Source, +) { + let caller = account("caller", u, SEED); + let value: BalanceOf = T::ProposalBondMinimum::get().saturating_mul(100.into()); + let _ = T::Currency::make_free_balance_be(&caller, value); + let beneficiary = account("beneficiary", u, SEED); + let beneficiary_lookup = T::Lookup::unlookup(beneficiary); + (caller, value, beneficiary_lookup) +} + +// Create the pre-requisite information needed to create a `report_awesome`. +fn setup_awesome(length: u32) -> (T::AccountId, Vec, T::AccountId) { + let caller = account("caller", 0, SEED); + let value = T::TipReportDepositBase::get() + + T::TipReportDepositPerByte::get() * length.into() + + T::Currency::minimum_balance(); + let _ = T::Currency::make_free_balance_be(&caller, value); + let reason = vec![0; length as usize]; + let awesome_person = account("awesome", 0, SEED); + (caller, reason, awesome_person) +} + +// Create the pre-requisite information needed to call `tip_new`. +fn setup_tip(r: u32, t: u32) -> + Result<(T::AccountId, Vec, T::AccountId, BalanceOf), &'static str> +{ + for i in 0 .. t { + let member = account("member", i, SEED); + T::Tippers::add(&member); + } + + ensure!(T::Tippers::count() == t as usize, "problem creating tippers"); + let caller = account("member", t - 1, SEED); + let reason = vec![0; r as usize]; + let beneficiary = account("beneficiary", t, SEED); + let value = T::Currency::minimum_balance().saturating_mul(100.into()); + Ok((caller, reason, beneficiary, value)) +} + +// Create `t` new types for the tip proposal with `hash`. +// This function automatically moves forward the block number to a time which +// would resolve the tipping process. +fn create_tips(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"); + Treasury::::tip(RawOrigin::Signed(caller).into(), hash, value)?; + } + frame_system::Module::::set_block_number(T::TipCountdown::get() * 10.into()); + Ok(()) +} + +// Create proposals that are approved for use in `on_initialize`. +fn create_approved_proposals(n: u32) -> Result<(), &'static str> { + for i in 0 .. n { + let (caller, value, lookup) = setup_proposal::(i); + Treasury::::propose_spend( + RawOrigin::Signed(caller).into(), + value, + lookup + )?; + let proposal_id = ProposalCount::get() - 1; + Treasury::::approve_proposal(RawOrigin::Root.into(), proposal_id)?; + } + ensure!(Approvals::get().len() == n as usize, "Not all approved"); + Ok(()) +} + +const MAX_BYTES: u32 = 16384; +const MAX_TIPPERS: u32 = 100; + +benchmarks! { + _ { } + + propose_spend { + let u in 0 .. 1000; + let (caller, value, beneficiary_lookup) = setup_proposal::(u); + }: _(RawOrigin::Signed(caller), value, beneficiary_lookup) + + reject_proposal { + let u in 0 .. 1000; + let (caller, value, beneficiary_lookup) = setup_proposal::(u); + Treasury::::propose_spend( + RawOrigin::Signed(caller).into(), + value, + beneficiary_lookup + )?; + let proposal_id = ProposalCount::get() - 1; + }: _(RawOrigin::Root, proposal_id) + + approve_proposal { + let u in 0 .. 1000; + let (caller, value, beneficiary_lookup) = setup_proposal::(u); + Treasury::::propose_spend( + RawOrigin::Signed(caller).into(), + value, + beneficiary_lookup + )?; + let proposal_id = ProposalCount::get() - 1; + }: _(RawOrigin::Root, proposal_id) + + report_awesome { + let r in 0 .. MAX_BYTES; + let (caller, reason, awesome_person) = setup_awesome::(r); + }: _(RawOrigin::Signed(caller), reason, awesome_person) + + retract_tip { + let r in 0 .. MAX_BYTES; + let (caller, reason, awesome_person) = setup_awesome::(r); + Treasury::::report_awesome( + RawOrigin::Signed(caller.clone()).into(), + reason.clone(), + awesome_person.clone() + )?; + let reason_hash = T::Hashing::hash(&reason[..]); + let hash = T::Hashing::hash_of(&(&reason_hash, &awesome_person)); + }: _(RawOrigin::Signed(caller), hash) + + tip_new { + let r in 0 .. MAX_BYTES; + let t in 1 .. MAX_TIPPERS; + + let (caller, reason, beneficiary, value) = setup_tip::(r, t)?; + }: _(RawOrigin::Signed(caller), reason, beneficiary, value) + + tip { + let t in 1 .. MAX_TIPPERS; + let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let value = T::Currency::minimum_balance().saturating_mul(100.into()); + Treasury::::tip_new( + RawOrigin::Signed(member).into(), + reason.clone(), + beneficiary.clone(), + 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)?; + let caller = account("member", t - 1, SEED); + }: _(RawOrigin::Signed(caller), hash, value) + + close_tip { + let t in 1 .. MAX_TIPPERS; + + // Make sure pot is funded + let pot_account = Treasury::::account_id(); + let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000.into()); + let _ = T::Currency::make_free_balance_be(&pot_account, value); + + // Set up a new tip proposal + let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let value = T::Currency::minimum_balance().saturating_mul(100.into()); + Treasury::::tip_new( + RawOrigin::Signed(member).into(), + reason.clone(), + beneficiary.clone(), + 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"); + create_tips::(t, hash.clone(), value)?; + + let caller = account("caller", t, SEED); + }: _(RawOrigin::Signed(caller), hash) + + on_initialize { + let p in 0 .. 100; + let pot_account = Treasury::::account_id(); + let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000.into()); + let _ = T::Currency::make_free_balance_be(&pot_account, value); + create_approved_proposals::(p)?; + }: { + Treasury::::on_initialize(T::BlockNumber::zero()); + } +} diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 72f9c508ca01f72c4050f1ed5fa626188f53a90f..27e0e852d6dee3dfe59f17fdd2ced7307b55be7a 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -92,15 +92,18 @@ use serde::{Serialize, Deserialize}; use sp_std::prelude::*; use frame_support::{decl_module, decl_storage, decl_event, ensure, print, decl_error, Parameter}; use frame_support::traits::{ - Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, ExistenceRequirement::AllowDeath, + Currency, Get, Imbalance, OnUnbalanced, ExistenceRequirement::KeepAlive, ReservableCurrency, WithdrawReason }; use sp_runtime::{Permill, ModuleId, Percent, RuntimeDebug, traits::{ Zero, EnsureOrigin, StaticLookup, AccountIdConversion, Saturating, Hash, BadOrigin }}; -use frame_support::{weights::SimpleDispatchInfo, traits::Contains}; +use frame_support::{weights::{Weight, WeighData, SimpleDispatchInfo}, traits::Contains}; use codec::{Encode, Decode}; -use frame_system::{self as system, ensure_signed}; +use frame_system::{self as system, ensure_signed, ensure_root}; + +mod tests; +mod benchmarking; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type PositiveImbalanceOf = <::Currency as Currency<::AccountId>>::PositiveImbalance; @@ -283,22 +286,8 @@ decl_error! { } } -mod migration { - use super::*; - pub fn migrate() { - for i in 0..ProposalCount::get() { - Proposals::::migrate_key_from_blake(i); - } - Reasons::::remove_all(); - } -} - decl_module! { pub struct Module for enum Call where origin: T::Origin { - fn on_runtime_upgrade() { - migration::migrate::(); - } - /// Fraction of a proposal's value that should be bonded in order to place the proposal. /// An accepted proposal gets these back. A rejected proposal does not. const ProposalBond: Permill = T::ProposalBond::get(); @@ -366,9 +355,11 @@ decl_module! { /// # #[weight = SimpleDispatchInfo::FixedOperational(100_000)] fn reject_proposal(origin, #[compact] proposal_id: ProposalIndex) { - T::RejectOrigin::ensure_origin(origin)?; - let proposal = >::take(&proposal_id).ok_or(Error::::InvalidProposalIndex)?; + T::RejectOrigin::try_origin(origin) + .map(|_| ()) + .or_else(ensure_root)?; + let proposal = >::take(&proposal_id).ok_or(Error::::InvalidProposalIndex)?; let value = proposal.bond; let imbalance = T::Currency::slash_reserved(&proposal.proposer, value).0; T::ProposalRejection::on_unbalanced(imbalance); @@ -386,10 +377,11 @@ decl_module! { /// # #[weight = SimpleDispatchInfo::FixedOperational(100_000)] fn approve_proposal(origin, #[compact] proposal_id: ProposalIndex) { - T::ApproveOrigin::ensure_origin(origin)?; + T::ApproveOrigin::try_origin(origin) + .map(|_| ()) + .or_else(ensure_root)?; ensure!(>::contains_key(proposal_id), Error::::InvalidProposalIndex); - Approvals::mutate(|v| v.push(proposal_id)); } @@ -559,11 +551,13 @@ decl_module! { Self::payout_tip(tip); } - fn on_finalize(n: T::BlockNumber) { + fn on_initialize(n: T::BlockNumber) -> Weight { // Check to see if we should spend some funds! if (n % T::SpendPeriod::get()).is_zero() { Self::spend_funds(); } + + SimpleDispatchInfo::default().weigh_data(()) } } } @@ -637,7 +631,7 @@ impl Module { Self::retain_active_tips(&mut tips); tips.sort_by_key(|i| i.1); let treasury = Self::account_id(); - let max_payout = T::Currency::free_balance(&treasury); + let max_payout = Self::pot(); let mut payout = tips[tips.len() / 2].1.min(max_payout); if let Some((finder, deposit)) = tip.finder { let _ = T::Currency::unreserve(&finder, deposit); @@ -647,11 +641,11 @@ impl Module { payout -= finders_fee; // this should go through given we checked it's at most the free balance, but still // we only make a best-effort. - let _ = T::Currency::transfer(&treasury, &finder, finders_fee, AllowDeath); + let _ = T::Currency::transfer(&treasury, &finder, finders_fee, KeepAlive); } } // same as above: best-effort only. - let _ = T::Currency::transfer(&treasury, &tip.who, payout, AllowDeath); + let _ = T::Currency::transfer(&treasury, &tip.who, payout, KeepAlive); } // Spend some money! @@ -703,7 +697,7 @@ impl Module { &Self::account_id(), imbalance, WithdrawReason::Transfer.into(), - ExistenceRequirement::KeepAlive + KeepAlive ) { print("Inconsistent state - couldn't settle imbalance for funds spent by treasury"); // Nothing else to do here. @@ -732,456 +726,3 @@ impl OnUnbalanced> for Module { Self::deposit_event(RawEvent::Deposit(numeric_amount)); } } - -#[cfg(test)] -mod tests { - use super::*; - - use frame_support::{assert_noop, assert_ok, impl_outer_origin, parameter_types, weights::Weight}; - use frame_support::traits::Contains; - use sp_core::H256; - use sp_runtime::{ - Perbill, - testing::Header, - traits::{BlakeTwo256, OnFinalize, IdentityLookup, BadOrigin}, - }; - - impl_outer_origin! { - pub enum Origin for Test where system = frame_system {} - } - - #[derive(Clone, Eq, PartialEq)] - pub struct Test; - 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::Trait for Test { - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Call = (); - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - type BlockHashCount = BlockHashCount; - type MaximumBlockWeight = MaximumBlockWeight; - type AvailableBlockRatio = AvailableBlockRatio; - type MaximumBlockLength = MaximumBlockLength; - type Version = (); - type ModuleToIndex = (); - type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); - type OnKilledAccount = (); - } - parameter_types! { - pub const ExistentialDeposit: u64 = 1; -} - impl pallet_balances::Trait for Test { - type Balance = u64; - type Event = (); - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - } - pub struct TenToFourteen; - impl Contains for TenToFourteen { - fn contains(n: &u64) -> bool { - *n >= 10 && *n <= 14 - } - fn sorted_members() -> Vec { - 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 TipCountdown: u64 = 1; - pub const TipFindersFee: Percent = Percent::from_percent(20); - pub const TipReportDepositBase: u64 = 1; - pub const TipReportDepositPerByte: u64 = 1; - } - impl Trait for Test { - type Currency = pallet_balances::Module; - type ApproveOrigin = frame_system::EnsureRoot; - type RejectOrigin = frame_system::EnsureRoot; - type Tippers = TenToFourteen; - type TipCountdown = TipCountdown; - type TipFindersFee = TipFindersFee; - type TipReportDepositBase = TipReportDepositBase; - type TipReportDepositPerByte = TipReportDepositPerByte; - type Event = (); - type ProposalRejection = (); - type ProposalBond = ProposalBond; - type ProposalBondMinimum = ProposalBondMinimum; - type SpendPeriod = SpendPeriod; - type Burn = Burn; - } - type System = frame_system::Module; - type Balances = pallet_balances::Module; - type Treasury = Module; - - 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(); - GenesisConfig::default().assimilate_storage::(&mut t).unwrap(); - t.into() - } - - #[test] - fn genesis_config_works() { - new_test_ext().execute_with(|| { - assert_eq!(Treasury::pot(), 0); - assert_eq!(Treasury::proposal_count(), 0); - }); - } - - fn tip_hash() -> H256 { - BlakeTwo256::hash_of(&(BlakeTwo256::hash(b"awesome.dot"), 3u64)) - } - - #[test] - fn tip_new_cannot_be_used_twice() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); - assert_noop!( - Treasury::tip_new(Origin::signed(11), b"awesome.dot".to_vec(), 3, 10), - Error::::AlreadyKnown - ); - }); - } - - #[test] - fn report_awesome_and_tip_works() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::report_awesome(Origin::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!( - Treasury::report_awesome(Origin::signed(1), b"awesome.dot".to_vec(), 3), - Error::::AlreadyKnown - ); - - let h = tip_hash(); - assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); - assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); - assert_noop!(Treasury::tip(Origin::signed(9), h.clone(), 10), BadOrigin); - System::set_block_number(2); - assert_ok!(Treasury::close_tip(Origin::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); - }); - } - - #[test] - fn report_awesome_from_beneficiary_and_tip_works() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::report_awesome(Origin::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"), 0u64)); - assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); - assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); - System::set_block_number(2); - assert_ok!(Treasury::close_tip(Origin::signed(100), h.into())); - assert_eq!(Balances::reserved_balance(0), 0); - assert_eq!(Balances::free_balance(0), 110); - }); - } - - #[test] - fn close_tip_works() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_eq!(Treasury::pot(), 100); - - assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); - let h = tip_hash(); - assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); - assert_noop!(Treasury::close_tip(Origin::signed(0), h.into()), Error::::StillOpen); - - assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); - assert_noop!(Treasury::close_tip(Origin::signed(0), h.into()), Error::::Premature); - - System::set_block_number(2); - assert_noop!(Treasury::close_tip(Origin::NONE, h.into()), BadOrigin); - assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); - assert_eq!(Balances::free_balance(3), 10); - - assert_noop!(Treasury::close_tip(Origin::signed(100), h.into()), Error::::UnknownTip); - }); - } - - #[test] - fn retract_tip_works() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); - let h = tip_hash(); - assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); - assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); - assert_noop!(Treasury::retract_tip(Origin::signed(10), h.clone()), Error::::NotFinder); - assert_ok!(Treasury::retract_tip(Origin::signed(0), h.clone())); - System::set_block_number(2); - assert_noop!(Treasury::close_tip(Origin::signed(0), h.into()), Error::::UnknownTip); - }); - } - - #[test] - fn tip_median_calculation_works() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 0)); - let h = tip_hash(); - assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 1000000)); - System::set_block_number(2); - assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); - assert_eq!(Balances::free_balance(3), 10); - }); - } - - #[test] - fn tip_changing_works() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10000)); - let h = tip_hash(); - assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10000)); - assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10000)); - assert_ok!(Treasury::tip(Origin::signed(13), h.clone(), 0)); - assert_ok!(Treasury::tip(Origin::signed(14), h.clone(), 0)); - assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 1000)); - assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 100)); - assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); - System::set_block_number(2); - assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); - assert_eq!(Balances::free_balance(3), 10); - }); - } - - #[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 spend_proposal_takes_min_deposit() { - new_test_ext().execute_with(|| { - assert_ok!(Treasury::propose_spend(Origin::signed(0), 1, 3)); - assert_eq!(Balances::free_balance(0), 99); - assert_eq!(Balances::reserved_balance(0), 1); - }); - } - - #[test] - fn spend_proposal_takes_proportional_deposit() { - new_test_ext().execute_with(|| { - assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); - assert_eq!(Balances::free_balance(0), 95); - assert_eq!(Balances::reserved_balance(0), 5); - }); - } - - #[test] - fn spend_proposal_fails_when_proposer_poor() { - new_test_ext().execute_with(|| { - assert_noop!( - Treasury::propose_spend(Origin::signed(2), 100, 3), - Error::::InsufficientProposersBalance, - ); - }); - } - - #[test] - 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)); - - >::on_finalize(1); - assert_eq!(Balances::free_balance(3), 0); - assert_eq!(Treasury::pot(), 100); - }); - } - - #[test] - fn unused_pot_should_diminish() { - new_test_ext().execute_with(|| { - let init_total_issuance = Balances::total_issuance(); - Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_eq!(Balances::total_issuance(), init_total_issuance + 100); - - >::on_finalize(2); - assert_eq!(Treasury::pot(), 50); - assert_eq!(Balances::total_issuance(), init_total_issuance + 50); - }); - } - - #[test] - 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)); - - >::on_finalize(2); - assert_eq!(Balances::free_balance(3), 0); - assert_eq!(Treasury::pot(), 50); - }); - } - - #[test] - 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::::InvalidProposalIndex); - }); - } - - #[test] - fn reject_non_existent_spend_proposal_fails() { - new_test_ext().execute_with(|| { - assert_noop!(Treasury::reject_proposal(Origin::ROOT, 0), Error::::InvalidProposalIndex); - }); - } - - #[test] - fn accept_non_existent_spend_proposal_fails() { - new_test_ext().execute_with(|| { - assert_noop!(Treasury::approve_proposal(Origin::ROOT, 0), Error::::InvalidProposalIndex); - }); - } - - #[test] - 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::::InvalidProposalIndex); - }); - } - - #[test] - fn accepted_spend_proposal_enacted_on_spend_period() { - new_test_ext().execute_with(|| { - 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)); - - >::on_finalize(2); - assert_eq!(Balances::free_balance(3), 100); - assert_eq!(Treasury::pot(), 0); - }); - } - - #[test] - fn pot_underflow_should_not_diminish() { - new_test_ext().execute_with(|| { - 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)); - - >::on_finalize(2); - assert_eq!(Treasury::pot(), 100); // Pot hasn't changed - - let _ = Balances::deposit_into_existing(&Treasury::account_id(), 100).unwrap(); - >::on_finalize(4); - assert_eq!(Balances::free_balance(3), 150); // Fund has been spent - assert_eq!(Treasury::pot(), 25); // Pot has finally changed - }); - } - - // Treasury account doesn't get deleted if amount approved to spend is all its free balance. - // i.e. pot should not include existential deposit needed for account survival. - #[test] - fn treasury_account_doesnt_get_deleted() { - new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&Treasury::account_id(), 101); - 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)); - - >::on_finalize(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)); - - >::on_finalize(4); - assert_eq!(Treasury::pot(), 0); // Pot is emptied - assert_eq!(Balances::free_balance(Treasury::account_id()), 1); // but the account is still there - }); - } - - // In case treasury account is not existing then it works fine. - // This is useful for chain that will just update runtime. - #[test] - fn inexistent_account_works() { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig::{ - balances: vec![(0, 100), (1, 99), (2, 1)], - }.assimilate_storage(&mut t).unwrap(); - // Treasury genesis config is not build thus treasury account does not exist - let mut t: sp_io::TestExternalities = t.into(); - - t.execute_with(|| { - 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)); - >::on_finalize(2); - assert_eq!(Treasury::pot(), 0); // Pot hasn't changed - assert_eq!(Balances::free_balance(3), 0); // Balance of `3` hasn't changed - - Balances::make_free_balance_be(&Treasury::account_id(), 100); - assert_eq!(Treasury::pot(), 99); // Pot now contains funds - assert_eq!(Balances::free_balance(Treasury::account_id()), 100); // Account does exist - - >::on_finalize(4); - - assert_eq!(Treasury::pot(), 0); // Pot has changed - assert_eq!(Balances::free_balance(3), 99); // Balance of `3` has changed - }); - } -} diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..1f6dbecef516e6fd9c9d62729a0245026017db35 --- /dev/null +++ b/frame/treasury/src/tests.rs @@ -0,0 +1,473 @@ +// Copyright 2020 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 . + +//! Treasury pallet tests. + +#![cfg(test)] + +use super::*; + +use frame_support::{ + assert_noop, assert_ok, impl_outer_origin, parameter_types, weights::Weight, + traits::{Contains, OnInitialize} +}; +use sp_core::H256; +use sp_runtime::{ + Perbill, + testing::Header, + traits::{BlakeTwo256, IdentityLookup, BadOrigin}, +}; + +impl_outer_origin! { + pub enum Origin for Test where system = frame_system {} +} + +#[derive(Clone, Eq, PartialEq)] +pub struct Test; +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::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = (); + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type AvailableBlockRatio = AvailableBlockRatio; + type MaximumBlockLength = MaximumBlockLength; + type Version = (); + type ModuleToIndex = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} +impl pallet_balances::Trait for Test { + type Balance = u64; + type Event = (); + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} +pub struct TenToFourteen; +impl Contains for TenToFourteen { + fn contains(n: &u64) -> bool { + *n >= 10 && *n <= 14 + } + fn sorted_members() -> Vec { + vec![10, 11, 12, 13, 14] + } + #[cfg(feature = "runtime-benchmarks")] + fn add(_: &u64) { unimplemented!() } +} +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 TipCountdown: u64 = 1; + pub const TipFindersFee: Percent = Percent::from_percent(20); + pub const TipReportDepositBase: u64 = 1; + pub const TipReportDepositPerByte: u64 = 1; +} +impl Trait for Test { + type Currency = pallet_balances::Module; + type ApproveOrigin = frame_system::EnsureRoot; + type RejectOrigin = frame_system::EnsureRoot; + type Tippers = TenToFourteen; + type TipCountdown = TipCountdown; + type TipFindersFee = TipFindersFee; + type TipReportDepositBase = TipReportDepositBase; + type TipReportDepositPerByte = TipReportDepositPerByte; + type Event = (); + type ProposalRejection = (); + type ProposalBond = ProposalBond; + type ProposalBondMinimum = ProposalBondMinimum; + type SpendPeriod = SpendPeriod; + type Burn = Burn; +} +type System = frame_system::Module; +type Balances = pallet_balances::Module; +type Treasury = Module; + +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(); + GenesisConfig::default().assimilate_storage::(&mut t).unwrap(); + t.into() +} + +#[test] +fn genesis_config_works() { + new_test_ext().execute_with(|| { + assert_eq!(Treasury::pot(), 0); + assert_eq!(Treasury::proposal_count(), 0); + }); +} + +fn tip_hash() -> H256 { + BlakeTwo256::hash_of(&(BlakeTwo256::hash(b"awesome.dot"), 3u64)) +} + +#[test] +fn tip_new_cannot_be_used_twice() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); + assert_noop!( + Treasury::tip_new(Origin::signed(11), b"awesome.dot".to_vec(), 3, 10), + Error::::AlreadyKnown + ); + }); +} + +#[test] +fn report_awesome_and_tip_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_ok!(Treasury::report_awesome(Origin::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!( + Treasury::report_awesome(Origin::signed(1), b"awesome.dot".to_vec(), 3), + Error::::AlreadyKnown + ); + + let h = tip_hash(); + assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); + assert_noop!(Treasury::tip(Origin::signed(9), h.clone(), 10), BadOrigin); + System::set_block_number(2); + assert_ok!(Treasury::close_tip(Origin::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); + }); +} + +#[test] +fn report_awesome_from_beneficiary_and_tip_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_ok!(Treasury::report_awesome(Origin::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"), 0u64)); + assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); + System::set_block_number(2); + assert_ok!(Treasury::close_tip(Origin::signed(100), h.into())); + assert_eq!(Balances::reserved_balance(0), 0); + assert_eq!(Balances::free_balance(0), 110); + }); +} + +#[test] +fn close_tip_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Treasury::pot(), 100); + + assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); + let h = tip_hash(); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); + assert_noop!(Treasury::close_tip(Origin::signed(0), h.into()), Error::::StillOpen); + + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); + assert_noop!(Treasury::close_tip(Origin::signed(0), h.into()), Error::::Premature); + + System::set_block_number(2); + assert_noop!(Treasury::close_tip(Origin::NONE, h.into()), BadOrigin); + assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); + assert_eq!(Balances::free_balance(3), 10); + + assert_noop!(Treasury::close_tip(Origin::signed(100), h.into()), Error::::UnknownTip); + }); +} + +#[test] +fn retract_tip_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_ok!(Treasury::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + let h = tip_hash(); + assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); + assert_noop!(Treasury::retract_tip(Origin::signed(10), h.clone()), Error::::NotFinder); + assert_ok!(Treasury::retract_tip(Origin::signed(0), h.clone())); + System::set_block_number(2); + assert_noop!(Treasury::close_tip(Origin::signed(0), h.into()), Error::::UnknownTip); + }); +} + +#[test] +fn tip_median_calculation_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 0)); + let h = tip_hash(); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 1000000)); + System::set_block_number(2); + assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); + assert_eq!(Balances::free_balance(3), 10); + }); +} + +#[test] +fn tip_changing_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10000)); + let h = tip_hash(); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10000)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10000)); + assert_ok!(Treasury::tip(Origin::signed(13), h.clone(), 0)); + assert_ok!(Treasury::tip(Origin::signed(14), h.clone(), 0)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 1000)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 100)); + assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); + System::set_block_number(2); + assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); + assert_eq!(Balances::free_balance(3), 10); + }); +} + +#[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 spend_proposal_takes_min_deposit() { + new_test_ext().execute_with(|| { + assert_ok!(Treasury::propose_spend(Origin::signed(0), 1, 3)); + assert_eq!(Balances::free_balance(0), 99); + assert_eq!(Balances::reserved_balance(0), 1); + }); +} + +#[test] +fn spend_proposal_takes_proportional_deposit() { + new_test_ext().execute_with(|| { + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); + assert_eq!(Balances::free_balance(0), 95); + assert_eq!(Balances::reserved_balance(0), 5); + }); +} + +#[test] +fn spend_proposal_fails_when_proposer_poor() { + new_test_ext().execute_with(|| { + assert_noop!( + Treasury::propose_spend(Origin::signed(2), 100, 3), + Error::::InsufficientProposersBalance, + ); + }); +} + +#[test] +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)); + + >::on_initialize(1); + assert_eq!(Balances::free_balance(3), 0); + assert_eq!(Treasury::pot(), 100); + }); +} + +#[test] +fn unused_pot_should_diminish() { + new_test_ext().execute_with(|| { + let init_total_issuance = Balances::total_issuance(); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Balances::total_issuance(), init_total_issuance + 100); + + >::on_initialize(2); + assert_eq!(Treasury::pot(), 50); + assert_eq!(Balances::total_issuance(), init_total_issuance + 50); + }); +} + +#[test] +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)); + + >::on_initialize(2); + assert_eq!(Balances::free_balance(3), 0); + assert_eq!(Treasury::pot(), 50); + }); +} + +#[test] +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::::InvalidProposalIndex); + }); +} + +#[test] +fn reject_non_existent_spend_proposal_fails() { + new_test_ext().execute_with(|| { + assert_noop!(Treasury::reject_proposal(Origin::ROOT, 0), Error::::InvalidProposalIndex); + }); +} + +#[test] +fn accept_non_existent_spend_proposal_fails() { + new_test_ext().execute_with(|| { + assert_noop!(Treasury::approve_proposal(Origin::ROOT, 0), Error::::InvalidProposalIndex); + }); +} + +#[test] +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::::InvalidProposalIndex); + }); +} + +#[test] +fn accepted_spend_proposal_enacted_on_spend_period() { + new_test_ext().execute_with(|| { + 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)); + + >::on_initialize(2); + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Treasury::pot(), 0); + }); +} + +#[test] +fn pot_underflow_should_not_diminish() { + new_test_ext().execute_with(|| { + 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)); + + >::on_initialize(2); + assert_eq!(Treasury::pot(), 100); // Pot hasn't changed + + let _ = Balances::deposit_into_existing(&Treasury::account_id(), 100).unwrap(); + >::on_initialize(4); + assert_eq!(Balances::free_balance(3), 150); // Fund has been spent + assert_eq!(Treasury::pot(), 25); // Pot has finally changed + }); +} + +// Treasury account doesn't get deleted if amount approved to spend is all its free balance. +// i.e. pot should not include existential deposit needed for account survival. +#[test] +fn treasury_account_doesnt_get_deleted() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + 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)); + + >::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)); + + >::on_initialize(4); + assert_eq!(Treasury::pot(), 0); // Pot is emptied + assert_eq!(Balances::free_balance(Treasury::account_id()), 1); // but the account is still there + }); +} + +// In case treasury account is not existing then it works fine. +// This is useful for chain that will just update runtime. +#[test] +fn inexistent_account_works() { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig::{ + balances: vec![(0, 100), (1, 99), (2, 1)], + }.assimilate_storage(&mut t).unwrap(); + // Treasury genesis config is not build thus treasury account does not exist + let mut t: sp_io::TestExternalities = t.into(); + + t.execute_with(|| { + 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)); + >::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 + + Balances::make_free_balance_be(&Treasury::account_id(), 100); + assert_eq!(Treasury::pot(), 99); // Pot now contains funds + assert_eq!(Balances::free_balance(Treasury::account_id()), 100); // Account does exist + + >::on_initialize(4); + + assert_eq!(Treasury::pot(), 0); // Pot has changed + assert_eq!(Balances::free_balance(3), 99); // Balance of `3` has changed + }); +} diff --git a/frame/utility/Cargo.toml b/frame/utility/Cargo.toml index 46f59a191ae9f9122b209ce15b20825d324f248f..cf1042d8521da3e0c8c538eaef51c48de03b5439 100644 --- a/frame/utility/Cargo.toml +++ b/frame/utility/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-utility" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,17 +10,19 @@ description = "FRAME utilities pallet" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } + +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } [features] default = ["std"] @@ -33,3 +35,10 @@ std = [ "sp-io/std", "sp-std/std" ] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", +] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/utility/src/benchmarking.rs b/frame/utility/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..f16754fad591e09c8e7e06abfb394a4ddc4c3c28 --- /dev/null +++ b/frame/utility/src/benchmarking.rs @@ -0,0 +1,148 @@ +// Copyright 2019-2020 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 . + +// Benchmarks for Utility Pallet + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_system::RawOrigin; +use frame_benchmarking::{benchmarks, account}; +use sp_runtime::traits::Saturating; + +use crate::Module as Utility; + +const SEED: u32 = 0; + +fn setup_multi(s: u32, z: u32) -> Result<(Vec, Box<::Call>), &'static str>{ + let mut signatories: Vec = Vec::new(); + for i in 0 .. s { + let signatory = account("signatory", i, SEED); + // Give them some balance for a possible deposit + let deposit = T::MultisigDepositBase::get() + T::MultisigDepositFactor::get() * s.into(); + let balance = T::Currency::minimum_balance().saturating_mul(100.into()) + deposit; + T::Currency::make_free_balance_be(&signatory, balance); + signatories.push(signatory); + } + signatories.sort(); + let call: Box<::Call> = Box::new(frame_system::Call::remark(vec![0; z as usize]).into()); + return Ok((signatories, call)) +} + +benchmarks! { + _ { } + + batch { + let c in 0 .. 1000; + let mut calls: Vec<::Call> = Vec::new(); + for i in 0 .. c { + let call = frame_system::Call::remark(vec![]).into(); + calls.push(call); + } + let caller = account("caller", 0, SEED); + }: _(RawOrigin::Signed(caller), calls) + + as_sub { + let u in 0 .. 1000; + let caller = account("caller", u, SEED); + let call = Box::new(frame_system::Call::remark(vec![]).into()); + }: _(RawOrigin::Signed(caller), u as u16, call) + + as_multi_create { + // 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 caller = signatories.pop().ok_or("signatories should have len 2 or more")?; + }: as_multi(RawOrigin::Signed(caller), s as u16, signatories, None, call) + + as_multi_approve { + // Signatories, need at least 2 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 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 = Utility::::timepoint(); + // Create the multi + Utility::::as_multi(RawOrigin::Signed(caller).into(), s as u16, signatories, None, call.clone())?; + let caller2 = signatories2.remove(0); + }: as_multi(RawOrigin::Signed(caller2), s as u16, signatories2, Some(timepoint), call) + + as_multi_complete { + // Signatories, need at least 2 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 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 = Utility::::timepoint(); + // Create the multi + Utility::::as_multi(RawOrigin::Signed(caller).into(), s as u16, signatories, None, call.clone())?; + // 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); + Utility::::as_multi(RawOrigin::Signed(caller_loop).into(), s as u16, signatories_loop, Some(timepoint), call.clone())?; + } + let caller2 = signatories2.remove(0); + }: as_multi(RawOrigin::Signed(caller2), s as u16, signatories2, Some(timepoint), call) + + approve_as_multi_create { + // Signatories, need at least 2 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 caller = signatories.pop().ok_or("signatories should have len 2 or more")?; + let call_hash = call.using_encoded(blake2_256); + // Create the multi + }: approve_as_multi(RawOrigin::Signed(caller), s as u16, signatories, None, call_hash) + + approve_as_multi_approve { + // Signatories, need at least 2 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 mut signatories2 = signatories.clone(); + let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; + let call_hash = call.using_encoded(blake2_256); + // before the call, get the timepoint + let timepoint = Utility::::timepoint(); + // Create the multi + Utility::::as_multi(RawOrigin::Signed(caller).into(), s as u16, signatories, None, call.clone())?; + let caller2 = signatories2.remove(0); + }: approve_as_multi(RawOrigin::Signed(caller2), s as u16, signatories2, Some(timepoint), call_hash) + + cancel_as_multi { + // Signatories, need at least 2 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 caller = signatories.pop().ok_or("signatories should have len 2 or more")?; + let call_hash = call.using_encoded(blake2_256); + let timepoint = Utility::::timepoint(); + // Create the multi + Utility::::as_multi(RawOrigin::Signed(caller.clone()).into(), s as u16, signatories.clone(), None, call.clone())?; + }: _(RawOrigin::Signed(caller), s as u16, signatories, timepoint, call_hash) +} diff --git a/frame/utility/src/lib.rs b/frame/utility/src/lib.rs index 6ecaadab37adecf947f66f5178c74bed224e7f2b..927d8b878631de2869de64b99f80b9e3f1d4c045 100644 --- a/frame/utility/src/lib.rs +++ b/frame/utility/src/lib.rs @@ -72,6 +72,9 @@ use frame_support::{traits::{Get, ReservableCurrency, Currency}, use frame_system::{self as system, ensure_signed}; use sp_runtime::{DispatchError, DispatchResult, traits::Dispatchable}; +mod tests; +mod benchmarking; + type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; /// Configuration trait. @@ -80,7 +83,7 @@ pub trait Trait: frame_system::Trait { type Event: From> + Into<::Event>; /// The overarching call type. - type Call: Parameter + Dispatchable + GetDispatchInfo; + type Call: Parameter + Dispatchable + GetDispatchInfo + From>; /// The currency mechanism. type Currency: ReservableCurrency; @@ -563,426 +566,3 @@ impl Module { Ok(signatories) } } - -#[cfg(test)] -mod tests { - use super::*; - - use frame_support::{ - assert_ok, assert_noop, impl_outer_origin, parameter_types, impl_outer_dispatch, - weights::Weight, impl_outer_event - }; - use sp_core::H256; - use sp_runtime::{Perbill, traits::{BlakeTwo256, IdentityLookup}, testing::Header}; - use crate as utility; - - impl_outer_origin! { - pub enum Origin for Test where system = frame_system {} - } - - impl_outer_event! { - pub enum TestEvent for Test { - system, - pallet_balances, - utility, - } - } - impl_outer_dispatch! { - pub enum Call for Test where origin: Origin { - pallet_balances::Balances, - utility::Utility, - } - } - - // For testing the pallet, we construct most of a mock runtime. This means - // first constructing a configuration type (`Test`) which `impl`s each of the - // configuration traits of pallets we want to use. - #[derive(Clone, Eq, PartialEq)] - pub struct Test; - 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::Trait for Test { - type Origin = Origin; - 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 = TestEvent; - type BlockHashCount = BlockHashCount; - type MaximumBlockWeight = MaximumBlockWeight; - type MaximumBlockLength = MaximumBlockLength; - type AvailableBlockRatio = AvailableBlockRatio; - type Version = (); - type ModuleToIndex = (); - type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); - type OnKilledAccount = (); - } - parameter_types! { - pub const ExistentialDeposit: u64 = 1; - } - impl pallet_balances::Trait for Test { - type Balance = u64; - type Event = TestEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - } - parameter_types! { - pub const MultisigDepositBase: u64 = 1; - pub const MultisigDepositFactor: u64 = 1; - pub const MaxSignatories: u16 = 3; - } - impl Trait for Test { - type Event = TestEvent; - type Call = Call; - type Currency = Balances; - type MultisigDepositBase = MultisigDepositBase; - type MultisigDepositFactor = MultisigDepositFactor; - type MaxSignatories = MaxSignatories; - } - type System = frame_system::Module; - type Balances = pallet_balances::Module; - type Utility = Module; - - use pallet_balances::Call as BalancesCall; - use pallet_balances::Error as BalancesError; - - 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, 10), (3, 10), (4, 10), (5, 10)], - }.assimilate_storage(&mut t).unwrap(); - t.into() - } - - fn last_event() -> TestEvent { - system::Module::::events().pop().map(|e| e.event).expect("Event expected") - } - - fn expect_event>(e: E) { - assert_eq!(last_event(), e.into()); - } - - fn now() -> Timepoint { - Utility::timepoint() - } - - #[test] - fn multisig_deposit_is_taken_and_returned() { - new_test_ext().execute_with(|| { - let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - assert_ok!(Utility::as_multi(Origin::signed(1), 2, vec![2, 3], None, call.clone())); - assert_eq!(Balances::free_balance(1), 2); - assert_eq!(Balances::reserved_balance(1), 3); - - assert_ok!(Utility::as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), call)); - assert_eq!(Balances::free_balance(1), 5); - assert_eq!(Balances::reserved_balance(1), 0); - }); - } - - #[test] - fn cancel_multisig_returns_deposit() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - let hash = call.using_encoded(blake2_256); - assert_ok!(Utility::approve_as_multi(Origin::signed(1), 3, vec![2, 3], None, hash.clone())); - assert_ok!(Utility::approve_as_multi(Origin::signed(2), 3, vec![1, 3], Some(now()), hash.clone())); - assert_eq!(Balances::free_balance(1), 6); - assert_eq!(Balances::reserved_balance(1), 4); - assert_ok!( - Utility::cancel_as_multi(Origin::signed(1), 3, vec![2, 3], now(), hash.clone()), - ); - assert_eq!(Balances::free_balance(1), 10); - assert_eq!(Balances::reserved_balance(1), 0); - }); - } - - #[test] - fn timepoint_checking_works() { - new_test_ext().execute_with(|| { - let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - let hash = call.using_encoded(blake2_256); - - assert_noop!( - Utility::approve_as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), hash.clone()), - Error::::UnexpectedTimepoint, - ); - - assert_ok!(Utility::approve_as_multi(Origin::signed(1), 2, vec![2, 3], None, hash)); - - assert_noop!( - Utility::as_multi(Origin::signed(2), 2, vec![1, 3], None, call.clone()), - Error::::NoTimepoint, - ); - let later = Timepoint { index: 1, .. now() }; - assert_noop!( - Utility::as_multi(Origin::signed(2), 2, vec![1, 3], Some(later), call.clone()), - Error::::WrongTimepoint, - ); - }); - } - - #[test] - fn multisig_2_of_3_works() { - new_test_ext().execute_with(|| { - let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - let hash = call.using_encoded(blake2_256); - assert_ok!(Utility::approve_as_multi(Origin::signed(1), 2, vec![2, 3], None, hash)); - assert_eq!(Balances::free_balance(6), 0); - - assert_ok!(Utility::as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), call)); - assert_eq!(Balances::free_balance(6), 15); - }); - } - - #[test] - fn multisig_3_of_3_works() { - new_test_ext().execute_with(|| { - let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - let hash = call.using_encoded(blake2_256); - assert_ok!(Utility::approve_as_multi(Origin::signed(1), 3, vec![2, 3], None, hash.clone())); - assert_ok!(Utility::approve_as_multi(Origin::signed(2), 3, vec![1, 3], Some(now()), hash.clone())); - assert_eq!(Balances::free_balance(6), 0); - - assert_ok!(Utility::as_multi(Origin::signed(3), 3, vec![1, 2], Some(now()), call)); - assert_eq!(Balances::free_balance(6), 15); - }); - } - - #[test] - fn cancel_multisig_works() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - let hash = call.using_encoded(blake2_256); - assert_ok!(Utility::approve_as_multi(Origin::signed(1), 3, vec![2, 3], None, hash.clone())); - assert_ok!(Utility::approve_as_multi(Origin::signed(2), 3, vec![1, 3], Some(now()), hash.clone())); - assert_noop!( - Utility::cancel_as_multi(Origin::signed(2), 3, vec![1, 3], now(), hash.clone()), - Error::::NotOwner, - ); - assert_ok!( - Utility::cancel_as_multi(Origin::signed(1), 3, vec![2, 3], now(), hash.clone()), - ); - }); - } - - #[test] - fn multisig_2_of_3_as_multi_works() { - new_test_ext().execute_with(|| { - let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - assert_ok!(Utility::as_multi(Origin::signed(1), 2, vec![2, 3], None, call.clone())); - assert_eq!(Balances::free_balance(6), 0); - - assert_ok!(Utility::as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), call)); - assert_eq!(Balances::free_balance(6), 15); - }); - } - - #[test] - fn multisig_2_of_3_as_multi_with_many_calls_works() { - new_test_ext().execute_with(|| { - let multi = Utility::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 call1 = Box::new(Call::Balances(BalancesCall::transfer(6, 10))); - let call2 = Box::new(Call::Balances(BalancesCall::transfer(7, 5))); - - assert_ok!(Utility::as_multi(Origin::signed(1), 2, vec![2, 3], None, call1.clone())); - assert_ok!(Utility::as_multi(Origin::signed(2), 2, vec![1, 3], None, call2.clone())); - assert_ok!(Utility::as_multi(Origin::signed(3), 2, vec![1, 2], Some(now()), call2)); - assert_ok!(Utility::as_multi(Origin::signed(3), 2, vec![1, 2], Some(now()), call1)); - - assert_eq!(Balances::free_balance(6), 10); - assert_eq!(Balances::free_balance(7), 5); - }); - } - - #[test] - fn multisig_2_of_3_cannot_reissue_same_call() { - new_test_ext().execute_with(|| { - let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 10))); - assert_ok!(Utility::as_multi(Origin::signed(1), 2, vec![2, 3], None, call.clone())); - assert_ok!(Utility::as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), call.clone())); - assert_eq!(Balances::free_balance(multi), 5); - - assert_ok!(Utility::as_multi(Origin::signed(1), 2, vec![2, 3], None, call.clone())); - assert_ok!(Utility::as_multi(Origin::signed(3), 2, vec![1, 2], Some(now()), call)); - - let err = DispatchError::from(BalancesError::::InsufficientBalance).stripped(); - expect_event(RawEvent::MultisigExecuted(3, now(), multi, Err(err))); - }); - } - - #[test] - fn zero_threshold_fails() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - assert_noop!( - Utility::as_multi(Origin::signed(1), 0, vec![2], None, call), - Error::::ZeroThreshold, - ); - }); - } - - #[test] - fn too_many_signatories_fails() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - assert_noop!( - Utility::as_multi(Origin::signed(1), 2, vec![2, 3, 4], None, call.clone()), - Error::::TooManySignatories, - ); - }); - } - - #[test] - fn duplicate_approvals_are_ignored() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - let hash = call.using_encoded(blake2_256); - assert_ok!(Utility::approve_as_multi(Origin::signed(1), 2, vec![2, 3], None, hash.clone())); - assert_noop!( - Utility::approve_as_multi(Origin::signed(1), 2, vec![2, 3], Some(now()), hash.clone()), - Error::::AlreadyApproved, - ); - assert_ok!(Utility::approve_as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), hash.clone())); - assert_noop!( - Utility::approve_as_multi(Origin::signed(3), 2, vec![1, 2], Some(now()), hash.clone()), - Error::::NoApprovalsNeeded, - ); - }); - } - - #[test] - fn multisig_1_of_3_works() { - new_test_ext().execute_with(|| { - let multi = Utility::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)); - - let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); - let hash = call.using_encoded(blake2_256); - assert_noop!( - Utility::approve_as_multi(Origin::signed(1), 1, vec![2, 3], None, hash.clone()), - Error::::NoApprovalsNeeded, - ); - assert_noop!( - Utility::as_multi(Origin::signed(4), 1, vec![2, 3], None, call.clone()), - BalancesError::::InsufficientBalance, - ); - assert_ok!(Utility::as_multi(Origin::signed(1), 1, vec![2, 3], None, call)); - - assert_eq!(Balances::free_balance(6), 15); - }); - } - - #[test] - fn as_sub_works() { - new_test_ext().execute_with(|| { - let sub_1_0 = Utility::sub_account_id(1, 0); - assert_ok!(Balances::transfer(Origin::signed(1), sub_1_0, 5)); - assert_noop!(Utility::as_sub( - Origin::signed(1), - 1, - Box::new(Call::Balances(BalancesCall::transfer(6, 3))), - ), BalancesError::::InsufficientBalance); - assert_ok!(Utility::as_sub( - Origin::signed(1), - 0, - Box::new(Call::Balances(BalancesCall::transfer(2, 3))), - )); - assert_eq!(Balances::free_balance(sub_1_0), 2); - assert_eq!(Balances::free_balance(2), 13); - }); - } - - #[test] - fn batch_with_root_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(1), 10); - assert_eq!(Balances::free_balance(2), 10); - assert_ok!(Utility::batch(Origin::ROOT, vec![ - Call::Balances(BalancesCall::force_transfer(1, 2, 5)), - Call::Balances(BalancesCall::force_transfer(1, 2, 5)) - ])); - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::free_balance(2), 20); - }); - } - - #[test] - fn batch_with_signed_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(1), 10); - assert_eq!(Balances::free_balance(2), 10); - assert_ok!( - Utility::batch(Origin::signed(1), vec![ - Call::Balances(BalancesCall::transfer(2, 5)), - Call::Balances(BalancesCall::transfer(2, 5)) - ]), - ); - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::free_balance(2), 20); - }); - } - - #[test] - fn batch_early_exit_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(1), 10); - assert_eq!(Balances::free_balance(2), 10); - assert_ok!( - Utility::batch(Origin::signed(1), vec![ - Call::Balances(BalancesCall::transfer(2, 5)), - Call::Balances(BalancesCall::transfer(2, 10)), - Call::Balances(BalancesCall::transfer(2, 5)), - ]), - ); - assert_eq!(Balances::free_balance(1), 5); - assert_eq!(Balances::free_balance(2), 15); - }); - } -} diff --git a/frame/utility/src/tests.rs b/frame/utility/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..9fcd525020a8096df1e6692678b9474b9bb89225 --- /dev/null +++ b/frame/utility/src/tests.rs @@ -0,0 +1,442 @@ +// Copyright 2019-2020 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 . + +// Tests for Utility Pallet + +#![cfg(test)] + +use super::*; + +use frame_support::{ + assert_ok, assert_noop, impl_outer_origin, parameter_types, impl_outer_dispatch, + weights::Weight, impl_outer_event +}; +use sp_core::H256; +use sp_runtime::{Perbill, traits::{BlakeTwo256, IdentityLookup}, testing::Header}; +use crate as utility; + +impl_outer_origin! { + pub enum Origin for Test where system = frame_system {} +} + +impl_outer_event! { + pub enum TestEvent for Test { + system, + pallet_balances, + utility, + } +} +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + frame_system::System, + pallet_balances::Balances, + utility::Utility, + } +} + +// For testing the pallet, we construct most of a mock runtime. This means +// first constructing a configuration type (`Test`) which `impl`s each of the +// configuration traits of pallets we want to use. +#[derive(Clone, Eq, PartialEq)] +pub struct Test; +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::Trait for Test { + type Origin = Origin; + 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 = TestEvent; + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} +impl pallet_balances::Trait for Test { + type Balance = u64; + type Event = TestEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} +parameter_types! { + pub const MultisigDepositBase: u64 = 1; + pub const MultisigDepositFactor: u64 = 1; + pub const MaxSignatories: u16 = 3; +} +impl Trait for Test { + type Event = TestEvent; + type Call = Call; + type Currency = Balances; + type MultisigDepositBase = MultisigDepositBase; + type MultisigDepositFactor = MultisigDepositFactor; + type MaxSignatories = MaxSignatories; +} +type System = frame_system::Module; +type Balances = pallet_balances::Module; +type Utility = Module; + +use pallet_balances::Call as BalancesCall; +use pallet_balances::Error as BalancesError; + +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, 10), (3, 10), (4, 10), (5, 10)], + }.assimilate_storage(&mut t).unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +fn last_event() -> TestEvent { + system::Module::::events().pop().map(|e| e.event).expect("Event expected") +} + +fn expect_event>(e: E) { + assert_eq!(last_event(), e.into()); +} + +fn now() -> Timepoint { + Utility::timepoint() +} + +#[test] +fn multisig_deposit_is_taken_and_returned() { + new_test_ext().execute_with(|| { + let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + assert_ok!(Utility::as_multi(Origin::signed(1), 2, vec![2, 3], None, call.clone())); + assert_eq!(Balances::free_balance(1), 2); + assert_eq!(Balances::reserved_balance(1), 3); + + assert_ok!(Utility::as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), call)); + assert_eq!(Balances::free_balance(1), 5); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn cancel_multisig_returns_deposit() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + let hash = call.using_encoded(blake2_256); + assert_ok!(Utility::approve_as_multi(Origin::signed(1), 3, vec![2, 3], None, hash.clone())); + assert_ok!(Utility::approve_as_multi(Origin::signed(2), 3, vec![1, 3], Some(now()), hash.clone())); + assert_eq!(Balances::free_balance(1), 6); + assert_eq!(Balances::reserved_balance(1), 4); + assert_ok!( + Utility::cancel_as_multi(Origin::signed(1), 3, vec![2, 3], now(), hash.clone()), + ); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn timepoint_checking_works() { + new_test_ext().execute_with(|| { + let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + let hash = call.using_encoded(blake2_256); + + assert_noop!( + Utility::approve_as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), hash.clone()), + Error::::UnexpectedTimepoint, + ); + + assert_ok!(Utility::approve_as_multi(Origin::signed(1), 2, vec![2, 3], None, hash)); + + assert_noop!( + Utility::as_multi(Origin::signed(2), 2, vec![1, 3], None, call.clone()), + Error::::NoTimepoint, + ); + let later = Timepoint { index: 1, .. now() }; + assert_noop!( + Utility::as_multi(Origin::signed(2), 2, vec![1, 3], Some(later), call.clone()), + Error::::WrongTimepoint, + ); + }); +} + +#[test] +fn multisig_2_of_3_works() { + new_test_ext().execute_with(|| { + let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + let hash = call.using_encoded(blake2_256); + assert_ok!(Utility::approve_as_multi(Origin::signed(1), 2, vec![2, 3], None, hash)); + assert_eq!(Balances::free_balance(6), 0); + + assert_ok!(Utility::as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), call)); + assert_eq!(Balances::free_balance(6), 15); + }); +} + +#[test] +fn multisig_3_of_3_works() { + new_test_ext().execute_with(|| { + let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + let hash = call.using_encoded(blake2_256); + assert_ok!(Utility::approve_as_multi(Origin::signed(1), 3, vec![2, 3], None, hash.clone())); + assert_ok!(Utility::approve_as_multi(Origin::signed(2), 3, vec![1, 3], Some(now()), hash.clone())); + assert_eq!(Balances::free_balance(6), 0); + + assert_ok!(Utility::as_multi(Origin::signed(3), 3, vec![1, 2], Some(now()), call)); + assert_eq!(Balances::free_balance(6), 15); + }); +} + +#[test] +fn cancel_multisig_works() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + let hash = call.using_encoded(blake2_256); + assert_ok!(Utility::approve_as_multi(Origin::signed(1), 3, vec![2, 3], None, hash.clone())); + assert_ok!(Utility::approve_as_multi(Origin::signed(2), 3, vec![1, 3], Some(now()), hash.clone())); + assert_noop!( + Utility::cancel_as_multi(Origin::signed(2), 3, vec![1, 3], now(), hash.clone()), + Error::::NotOwner, + ); + assert_ok!( + Utility::cancel_as_multi(Origin::signed(1), 3, vec![2, 3], now(), hash.clone()), + ); + }); +} + +#[test] +fn multisig_2_of_3_as_multi_works() { + new_test_ext().execute_with(|| { + let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + assert_ok!(Utility::as_multi(Origin::signed(1), 2, vec![2, 3], None, call.clone())); + assert_eq!(Balances::free_balance(6), 0); + + assert_ok!(Utility::as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), call)); + assert_eq!(Balances::free_balance(6), 15); + }); +} + +#[test] +fn multisig_2_of_3_as_multi_with_many_calls_works() { + new_test_ext().execute_with(|| { + let multi = Utility::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 call1 = Box::new(Call::Balances(BalancesCall::transfer(6, 10))); + let call2 = Box::new(Call::Balances(BalancesCall::transfer(7, 5))); + + assert_ok!(Utility::as_multi(Origin::signed(1), 2, vec![2, 3], None, call1.clone())); + assert_ok!(Utility::as_multi(Origin::signed(2), 2, vec![1, 3], None, call2.clone())); + assert_ok!(Utility::as_multi(Origin::signed(3), 2, vec![1, 2], Some(now()), call2)); + assert_ok!(Utility::as_multi(Origin::signed(3), 2, vec![1, 2], Some(now()), call1)); + + assert_eq!(Balances::free_balance(6), 10); + assert_eq!(Balances::free_balance(7), 5); + }); +} + +#[test] +fn multisig_2_of_3_cannot_reissue_same_call() { + new_test_ext().execute_with(|| { + let multi = Utility::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 = Box::new(Call::Balances(BalancesCall::transfer(6, 10))); + assert_ok!(Utility::as_multi(Origin::signed(1), 2, vec![2, 3], None, call.clone())); + assert_ok!(Utility::as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), call.clone())); + assert_eq!(Balances::free_balance(multi), 5); + + assert_ok!(Utility::as_multi(Origin::signed(1), 2, vec![2, 3], None, call.clone())); + assert_ok!(Utility::as_multi(Origin::signed(3), 2, vec![1, 2], Some(now()), call)); + + let err = DispatchError::from(BalancesError::::InsufficientBalance).stripped(); + expect_event(RawEvent::MultisigExecuted(3, now(), multi, Err(err))); + }); +} + +#[test] +fn zero_threshold_fails() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + assert_noop!( + Utility::as_multi(Origin::signed(1), 0, vec![2], None, call), + Error::::ZeroThreshold, + ); + }); +} + +#[test] +fn too_many_signatories_fails() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + assert_noop!( + Utility::as_multi(Origin::signed(1), 2, vec![2, 3, 4], None, call.clone()), + Error::::TooManySignatories, + ); + }); +} + +#[test] +fn duplicate_approvals_are_ignored() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + let hash = call.using_encoded(blake2_256); + assert_ok!(Utility::approve_as_multi(Origin::signed(1), 2, vec![2, 3], None, hash.clone())); + assert_noop!( + Utility::approve_as_multi(Origin::signed(1), 2, vec![2, 3], Some(now()), hash.clone()), + Error::::AlreadyApproved, + ); + assert_ok!(Utility::approve_as_multi(Origin::signed(2), 2, vec![1, 3], Some(now()), hash.clone())); + assert_noop!( + Utility::approve_as_multi(Origin::signed(3), 2, vec![1, 2], Some(now()), hash.clone()), + Error::::NoApprovalsNeeded, + ); + }); +} + +#[test] +fn multisig_1_of_3_works() { + new_test_ext().execute_with(|| { + let multi = Utility::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)); + + let call = Box::new(Call::Balances(BalancesCall::transfer(6, 15))); + let hash = call.using_encoded(blake2_256); + assert_noop!( + Utility::approve_as_multi(Origin::signed(1), 1, vec![2, 3], None, hash.clone()), + Error::::NoApprovalsNeeded, + ); + assert_noop!( + Utility::as_multi(Origin::signed(4), 1, vec![2, 3], None, call.clone()), + BalancesError::::InsufficientBalance, + ); + assert_ok!(Utility::as_multi(Origin::signed(1), 1, vec![2, 3], None, call)); + + assert_eq!(Balances::free_balance(6), 15); + }); +} + +#[test] +fn as_sub_works() { + new_test_ext().execute_with(|| { + let sub_1_0 = Utility::sub_account_id(1, 0); + assert_ok!(Balances::transfer(Origin::signed(1), sub_1_0, 5)); + assert_noop!(Utility::as_sub( + Origin::signed(1), + 1, + Box::new(Call::Balances(BalancesCall::transfer(6, 3))), + ), BalancesError::::InsufficientBalance); + assert_ok!(Utility::as_sub( + Origin::signed(1), + 0, + Box::new(Call::Balances(BalancesCall::transfer(2, 3))), + )); + assert_eq!(Balances::free_balance(sub_1_0), 2); + assert_eq!(Balances::free_balance(2), 13); + }); +} + +#[test] +fn batch_with_root_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::batch(Origin::ROOT, vec![ + Call::Balances(BalancesCall::force_transfer(1, 2, 5)), + Call::Balances(BalancesCall::force_transfer(1, 2, 5)) + ])); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 20); + }); +} + +#[test] +fn batch_with_signed_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!( + Utility::batch(Origin::signed(1), vec![ + Call::Balances(BalancesCall::transfer(2, 5)), + Call::Balances(BalancesCall::transfer(2, 5)) + ]), + ); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 20); + }); +} + +#[test] +fn batch_early_exit_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!( + Utility::batch(Origin::signed(1), vec![ + Call::Balances(BalancesCall::transfer(2, 5)), + Call::Balances(BalancesCall::transfer(2, 10)), + Call::Balances(BalancesCall::transfer(2, 5)), + ]), + ); + assert_eq!(Balances::free_balance(1), 5); + assert_eq!(Balances::free_balance(2), 15); + }); +} diff --git a/frame/vesting/Cargo.toml b/frame/vesting/Cargo.toml index 882c062c438355a29b68d63d8124de4a100e2b27..e40062706ff8559101e0390300b2a8af58a4174c 100644 --- a/frame/vesting/Cargo.toml +++ b/frame/vesting/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-vesting" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,19 +10,19 @@ description = "FRAME pallet for manage vesting" [dependencies] serde = { version = "1.0.101", optional = true } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } enumflags2 = { version = "0.6.2" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } -frame-benchmarking = { version = "2.0.0-alpha.2", default-features = false, path = "../benchmarking", optional = true } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } +frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } [dev-dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } -sp-storage = { version = "2.0.0-alpha.2", path = "../../primitives/storage" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" } +sp-storage = { version = "2.0.0-alpha.5", path = "../../primitives/storage" } hex-literal = "0.2.1" [features] @@ -36,4 +36,7 @@ std = [ "frame-support/std", "frame-system/std", ] -runtime-benchmarks = ["frame-benchmarking", "frame-system/runtime-benchmarks"] +runtime-benchmarks = ["frame-benchmarking"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/vesting/src/benchmarking.rs b/frame/vesting/src/benchmarking.rs index 79ab0cb6e329ee577d094d5567bde037550379ab..2ef8ed9ef8dbb867136b37ceb43b85cdb3b21fc7 100644 --- a/frame/vesting/src/benchmarking.rs +++ b/frame/vesting/src/benchmarking.rs @@ -16,6 +16,8 @@ //! Vesting pallet benchmarking. +#![cfg(feature = "runtime-benchmarks")] + use super::*; use frame_system::{RawOrigin, Module as System}; @@ -44,6 +46,7 @@ fn setup(b: u32) -> T::AccountId { let starting_block = 0; let caller = account("caller", 0, SEED); + System::::set_block_number(0.into()); // Add schedule to avoid `NotVesting` error. let _ = Vesting::::add_vesting_schedule( @@ -63,32 +66,44 @@ fn setup(b: u32) -> T::AccountId { benchmarks! { _ { - // Current block. It allows to hit different paths of `update_lock`. - // It doesn't seems to influence the timings which branch is taken. - let b in 0 .. 1 => (); // Number of previous locks. // It doesn't seems to influence the timings for lower values. let l in 0 .. MAX_LOCKS => add_locks::(l); } - vest { - let b in ...; + vest_locked { let l in ...; - let caller = setup::(b); + let caller = setup::(0u32); - }: _(RawOrigin::Signed(caller)) + }: vest(RawOrigin::Signed(caller)) - vest_other { - let b in ...; + vest_not_locked { let l in ...; - let other: T::AccountId = setup::(b); + let caller = setup::(1u32); + + }: vest(RawOrigin::Signed(caller)) + + vest_other_locked { + let l in ...; + + let other: T::AccountId = setup::(0u32); let other_lookup: ::Source = T::Lookup::unlookup(other.clone()); let caller = account("caller", 0, SEED); - }: _(RawOrigin::Signed(caller), other_lookup) + }: vest_other(RawOrigin::Signed(caller), other_lookup) + + vest_other_not_locked { + let l in ...; + + let other: T::AccountId = setup::(1u32); + let other_lookup: ::Source = T::Lookup::unlookup(other.clone()); + + let caller = account("caller", 0, SEED); + + }: vest_other(RawOrigin::Signed(caller), other_lookup) vested_transfer { let u in 0 .. 1000; @@ -106,6 +121,6 @@ benchmarks! { }; let _ = T::Currency::make_free_balance_be(&from, transfer_amount * 10.into()); - + }: _(RawOrigin::Signed(from), to_lookup, vesting_schedule) } diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index 0b670de3b4a5c4abcd5d8310a0f9cce53fe13708..b0c98e78bd6f7243e4c017f133726fccf16407a9 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -53,11 +53,13 @@ use sp_runtime::{DispatchResult, RuntimeDebug, traits::{ StaticLookup, Zero, AtLeast32Bit, MaybeSerializeDeserialize, Convert }}; use frame_support::{decl_module, decl_event, decl_storage, decl_error, ensure}; -use frame_support::traits::{Currency, LockableCurrency, VestingSchedule, WithdrawReason, LockIdentifier, ExistenceRequirement, Get, MigrateAccount}; +use frame_support::traits::{ + Currency, LockableCurrency, VestingSchedule, WithdrawReason, LockIdentifier, + ExistenceRequirement, Get +}; use frame_support::weights::SimpleDispatchInfo; use frame_system::{self as system, ensure_signed}; -#[cfg(feature = "runtime-benchmarks")] mod benchmarking; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -192,6 +194,7 @@ decl_module! { /// - One storage read (codec `O(1)`) and up to one removal. /// - One event. /// # + #[weight = SimpleDispatchInfo::default()] fn vest(origin) -> DispatchResult { let who = ensure_signed(origin)?; Self::update_lock(who) @@ -213,6 +216,7 @@ decl_module! { /// - One storage read (codec `O(1)`) and up to one removal. /// - One event. /// # + #[weight = SimpleDispatchInfo::default()] fn vest_other(origin, target: ::Source) -> DispatchResult { ensure_signed(origin)?; Self::update_lock(T::Lookup::lookup(target)?) @@ -254,12 +258,6 @@ decl_module! { } } -impl MigrateAccount for Module { - fn migrate_account(a: &T::AccountId) { - Vesting::::migrate_key_from_blake(a); - } -} - impl Module { /// (Re)set or remove the module's currency lock on `who`'s account in accordance with their /// current unvested amount. @@ -388,7 +386,7 @@ mod tests { type Version = (); type ModuleToIndex = (); type AccountData = pallet_balances::AccountData; - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } impl pallet_balances::Trait for Test { @@ -453,7 +451,9 @@ mod tests { (12, 10, 20, 5 * self.existential_deposit) ], }.assimilate_storage(&mut t).unwrap(); - t.into() + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext } } @@ -463,7 +463,6 @@ mod tests { .existential_deposit(256) .build() .execute_with(|| { - assert_eq!(System::block_number(), 1); let user1_free_balance = Balances::free_balance(&1); let user2_free_balance = Balances::free_balance(&2); let user12_free_balance = Balances::free_balance(&12); @@ -522,7 +521,6 @@ mod tests { .existential_deposit(10) .build() .execute_with(|| { - assert_eq!(System::block_number(), 1); let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance // Account 1 has only 5 units vested at block 1 (plus 50 unvested) @@ -540,7 +538,6 @@ mod tests { .existential_deposit(10) .build() .execute_with(|| { - assert_eq!(System::block_number(), 1); let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance // Account 1 has only 5 units vested at block 1 (plus 50 unvested) @@ -556,7 +553,6 @@ mod tests { .existential_deposit(10) .build() .execute_with(|| { - assert_eq!(System::block_number(), 1); let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance // Account 1 has only 5 units vested at block 1 (plus 50 unvested) @@ -572,7 +568,6 @@ mod tests { .existential_deposit(10) .build() .execute_with(|| { - assert_eq!(System::block_number(), 1); assert_ok!(Balances::transfer(Some(3).into(), 1, 100)); assert_ok!(Balances::transfer(Some(3).into(), 2, 100)); @@ -600,7 +595,6 @@ mod tests { .existential_deposit(256) .build() .execute_with(|| { - assert_eq!(System::block_number(), 1); let user12_free_balance = Balances::free_balance(&12); assert_eq!(user12_free_balance, 2560); // Account 12 has free balance @@ -626,7 +620,6 @@ mod tests { .existential_deposit(256) .build() .execute_with(|| { - assert_eq!(System::block_number(), 1); let user3_free_balance = Balances::free_balance(&3); let user4_free_balance = Balances::free_balance(&4); assert_eq!(user3_free_balance, 256 * 30); @@ -670,7 +663,6 @@ mod tests { .existential_deposit(256) .build() .execute_with(|| { - assert_eq!(System::block_number(), 1); let user2_free_balance = Balances::free_balance(&2); let user4_free_balance = Balances::free_balance(&4); assert_eq!(user2_free_balance, 256 * 20); diff --git a/primitives/allocator/Cargo.toml b/primitives/allocator/Cargo.toml index 944cfa6de19d57c8f9a010b5b1ab831f69adf31f..635a3c9128febcd6fd46690ea3e91043d935e70f 100644 --- a/primitives/allocator/Cargo.toml +++ b/primitives/allocator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-allocator" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,9 +10,9 @@ description = "Collection of allocator implementations." documentation = "https://docs.rs/sp-allocator" [dependencies] -sp-std = { version = "2.0.0-alpha.2", path = "../std", default-features = false } -sp-core = { version = "2.0.0-alpha.2", path = "../core", default-features = false } -sp-wasm-interface = { version = "2.0.0-alpha.2", path = "../wasm-interface", default-features = false } +sp-std = { version = "2.0.0-alpha.5", path = "../std", default-features = false } +sp-core = { version = "2.0.0-alpha.5", path = "../core", default-features = false } +sp-wasm-interface = { version = "2.0.0-alpha.5", path = "../wasm-interface", default-features = false } log = { version = "0.4.8", optional = true } derive_more = { version = "0.99.2", optional = true } @@ -25,3 +25,6 @@ std = [ "log", "derive_more", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/api/Cargo.toml b/primitives/api/Cargo.toml index a33ff0fa0d75453077cec6650c8c62af54022e91..fedeeceb3faadfb8e3ec6ff19b7204aa97e6904b 100644 --- a/primitives/api/Cargo.toml +++ b/primitives/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-api" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,13 +9,13 @@ repository = "https://github.com/paritytech/substrate/" description = "Substrate runtime api primitives" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -sp-api-proc-macro = { version = "2.0.0-alpha.2", path = "proc-macro" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } -sp-version = { version = "2.0.0-alpha.2", default-features = false, path = "../version" } -sp-state-machine = { version = "0.8.0-alpha.2", optional = true, path = "../../primitives/state-machine" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +sp-api-proc-macro = { version = "2.0.0-alpha.5", path = "proc-macro" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } +sp-version = { version = "2.0.0-alpha.5", default-features = false, path = "../version" } +sp-state-machine = { version = "0.8.0-alpha.5", optional = true, path = "../../primitives/state-machine" } hash-db = { version = "0.15.2", optional = true } [dev-dependencies] @@ -32,3 +32,6 @@ std = [ "sp-version/std", "hash-db", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/api/proc-macro/Cargo.toml b/primitives/api/proc-macro/Cargo.toml index 940e21759642fdd12fc6339a34a2e67410f956ac..25c5ae13435a5a107cee146acc2a0661afea8c9f 100644 --- a/primitives/api/proc-macro/Cargo.toml +++ b/primitives/api/proc-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-api-proc-macro" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -24,3 +24,6 @@ proc-macro-crate = "0.1.4" [features] default = [ "std" ] std = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/api/proc-macro/src/decl_runtime_apis.rs b/primitives/api/proc-macro/src/decl_runtime_apis.rs index 459707bdb53f4425153d7dc1bc631566379fc092..e9f3087912e26f322214fa22cdbca397a4a2c332 100644 --- a/primitives/api/proc-macro/src/decl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/decl_runtime_apis.rs @@ -19,7 +19,7 @@ use crate::utils::{ fold_fn_decl_for_client_side, extract_parameter_names_types_and_borrows, generate_native_call_generator_fn_name, return_type_extract_type, generate_method_runtime_api_impl_name, generate_call_api_at_fn_name, prefix_function_with_trait, - replace_wild_card_parameter_names, + replace_wild_card_parameter_names, AllowSelfRefInParameters, }; use proc_macro2::{TokenStream, Span}; @@ -198,7 +198,7 @@ fn generate_native_call_generators(decl: &ItemTrait) -> Result { // 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_)?; + let params = extract_parameter_names_types_and_borrows(&fn_, AllowSelfRefInParameters::No)?; let trait_fn_name = &fn_.ident; let fn_name = generate_native_call_generator_fn_name(&fn_.ident); let output = return_type_replace_block_with_node_block(fn_.output.clone()); @@ -592,7 +592,10 @@ impl<'a> ToClientSideDecl<'a> { // 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) { + 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; @@ -629,7 +632,10 @@ impl<'a> ToClientSideDecl<'a> { mut method: TraitItemMethod, context: TokenStream, ) -> TraitItemMethod { - let params = match extract_parameter_names_types_and_borrows(&method.sig) { + let params = match extract_parameter_names_types_and_borrows( + &method.sig, + AllowSelfRefInParameters::No, + ) { Ok(res) => res.into_iter().map(|v| v.0).collect::>(), Err(e) => { self.errors.push(e.to_compile_error()); @@ -780,7 +786,7 @@ fn generate_runtime_api_id(trait_name: &str) -> TokenStream { let mut res = [0; 8]; res.copy_from_slice(blake2_rfc::blake2b::blake2b(8, &[], trait_name.as_bytes()).as_bytes()); - quote!( const ID: [u8; 8] = [ #( #res ),* ]; ) + quote!( const ID: [u8; 8] = [ #( #res ),* ]; ) } /// Generates the const variable that holds the runtime api version. diff --git a/primitives/api/proc-macro/src/impl_runtime_apis.rs b/primitives/api/proc-macro/src/impl_runtime_apis.rs index e16cf3b5c46f78f50fbc03f5bff0f699dbedf503..7def6aa0fb73af3cca94b33ab1bb9ed7d6a8f70e 100644 --- a/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -19,7 +19,8 @@ use crate::utils::{ generate_runtime_mod_name_for_trait, generate_method_runtime_api_impl_name, extract_parameter_names_types_and_borrows, generate_native_call_generator_fn_name, return_type_extract_type, generate_call_api_at_fn_name, prefix_function_with_trait, - extract_all_signature_types, + extract_all_signature_types, extract_block_type_from_trait_path, extract_impl_trait, + AllowSelfRefInParameters, RequireQualifiedTraitPath, }; use proc_macro2::{Span, TokenStream}; @@ -66,7 +67,7 @@ fn generate_impl_call( input: &Ident, impl_trait: &Path ) -> Result { - let params = extract_parameter_names_types_and_borrows(signature)?; + let params = extract_parameter_names_types_and_borrows(signature, AllowSelfRefInParameters::No)?; let c = generate_crate_access(HIDDEN_INCLUDES_ID); let c_iter = iter::repeat(&c); @@ -93,50 +94,6 @@ fn generate_impl_call( ) } -/// Extract the trait that is implemented in the given `ItemImpl`. -fn extract_impl_trait<'a>(impl_: &'a ItemImpl) -> Result<&'a Path> { - impl_.trait_.as_ref().map(|v| &v.1).ok_or_else( - || Error::new(impl_.span(), "Only implementation of traits are supported!") - ).and_then(|p| { - if p.segments.len() > 1 { - Ok(p) - } else { - Err( - Error::new( - p.span(), - "The implemented trait has to be referenced with a path, \ - e.g. `impl client::Core for Runtime`." - ) - ) - } - }) -} - -/// Extracts the runtime block identifier. -fn extract_runtime_block_ident(trait_: &Path) -> Result<&TypePath> { - let span = trait_.span(); - let generics = trait_ - .segments - .last() - .ok_or_else(|| Error::new(span, "Empty path not supported"))?; - - match &generics.arguments { - PathArguments::AngleBracketed(ref args) => { - args.args.first().and_then(|v| match v { - GenericArgument::Type(Type::Path(ref block)) => Some(block), - _ => None - }).ok_or_else(|| Error::new(args.span(), "Missing `Block` generic parameter.")) - }, - PathArguments::None => { - let span = trait_.segments.last().as_ref().unwrap().span(); - Err(Error::new(span, "Missing `Block` generic parameter.")) - }, - PathArguments::Parenthesized(_) => { - Err(Error::new(generics.arguments.span(), "Unexpected parentheses in path!")) - } - } -} - /// Generate all the implementation calls for the given functions. fn generate_impl_calls( impls: &[ItemImpl], @@ -145,7 +102,7 @@ fn generate_impl_calls( let mut impl_calls = Vec::new(); for impl_ in impls { - let impl_trait_path = extract_impl_trait(impl_)?; + 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_ident = &impl_trait_path .segments @@ -307,11 +264,19 @@ fn generate_runtime_api_base_structures() -> Result { res } - fn runtime_version_at( + fn has_api( + &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)) + } + + fn has_api_with bool>( &self, at: &#crate_::BlockId, - ) -> std::result::Result<#crate_::RuntimeVersion, C::Error> { - self.call.runtime_version_at(at) + pred: P, + ) -> std::result::Result where Self: Sized { + self.call.runtime_version_at(at).map(|v| v.has_api_with(&A::ID, pred)) } fn record_proof(&mut self) { @@ -450,7 +415,7 @@ fn generate_api_impl_for_runtime(impls: &[ItemImpl]) -> Result { // we put the `RuntimeBlock` as first argument for the trait generics. for impl_ in impls.iter() { let mut impl_ = impl_.clone(); - let trait_ = extract_impl_trait(&impl_)?.clone(); + let trait_ = extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(); let trait_ = extend_with_runtime_decl_path(trait_); impl_.trait_.as_mut().unwrap().1 = trait_; @@ -506,7 +471,10 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { }).collect::>() }; - let (param_types, error) = match extract_parameter_names_types_and_borrows(&input.sig) { + 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; @@ -645,13 +613,13 @@ 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_)?; + 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 runtime_block = extract_runtime_block_ident(impl_trait_path)?; + 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 @@ -682,7 +650,9 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result { let mut processed_traits = HashSet::new(); for impl_ in impls { - let mut path = extend_with_runtime_decl_path(extract_impl_trait(&impl_)?.clone()); + let mut path = extend_with_runtime_decl_path( + extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(), + ); // Remove the trait let trait_ = path .segments diff --git a/primitives/api/proc-macro/src/lib.rs b/primitives/api/proc-macro/src/lib.rs index 9e5908717c18955cf45e3fefb4a95d12ea9d7fb8..12f435bd166dff2a579346ff8a710faec6bf89ba 100644 --- a/primitives/api/proc-macro/src/lib.rs +++ b/primitives/api/proc-macro/src/lib.rs @@ -21,6 +21,7 @@ use proc_macro::TokenStream; mod impl_runtime_apis; +mod mock_impl_runtime_apis; mod decl_runtime_apis; mod utils; @@ -29,6 +30,11 @@ pub fn impl_runtime_apis(input: TokenStream) -> TokenStream { impl_runtime_apis::impl_runtime_apis_impl(input) } +#[proc_macro] +pub fn mock_impl_runtime_apis(input: TokenStream) -> TokenStream { + mock_impl_runtime_apis::mock_impl_runtime_apis_impl(input) +} + #[proc_macro] pub fn decl_runtime_apis(input: TokenStream) -> TokenStream { decl_runtime_apis::decl_runtime_apis_impl(input) diff --git a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs new file mode 100644 index 0000000000000000000000000000000000000000..0767c804a637e2983c52affc97c69f4aea47ce62 --- /dev/null +++ b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs @@ -0,0 +1,378 @@ +// Copyright 2020 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 crate::utils::{ + generate_crate_access, generate_hidden_includes, + generate_method_runtime_api_impl_name, extract_parameter_names_types_and_borrows, + return_type_extract_type, extract_block_type_from_trait_path, extract_impl_trait, + AllowSelfRefInParameters, RequireQualifiedTraitPath, +}; + +use proc_macro2::{Span, TokenStream}; + +use quote::quote; + +use syn::{ + spanned::Spanned, parse_macro_input, Ident, Type, ItemImpl, ImplItem, TypePath, parse_quote, + parse::{Parse, ParseStream, Result, Error}, fold::{self, Fold}, +}; + +/// Unique identifier used to make the hidden includes unique for this macro. +const HIDDEN_INCLUDES_ID: &str = "MOCK_IMPL_RUNTIME_APIS"; + +/// The structure used for parsing the runtime api implementations. +struct RuntimeApiImpls { + impls: Vec, +} + +impl Parse for RuntimeApiImpls { + fn parse(input: ParseStream) -> Result { + let mut impls = Vec::new(); + + while !input.is_empty() { + impls.push(ItemImpl::parse(input)?); + } + + if impls.is_empty() { + Err(Error::new(Span::call_site(), "No api implementation given!")) + } else { + Ok(Self { impls }) + } + } +} + +/// Implement the `ApiExt` trait, `ApiErrorExt` trait and the `Core` runtime api. +fn implement_common_api_traits( + error_type: Option, + block_type: TypePath, + self_ty: Type, +) -> Result { + let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); + + let error_type = error_type.map(|e| quote!(#e)).unwrap_or_else(|| quote!(String)); + + Ok(quote!( + impl #crate_::ApiErrorExt for #self_ty { + type Error = #error_type; + } + + impl #crate_::ApiExt<#block_type> for #self_ty { + type StateBackend = #crate_::InMemoryBackend<#crate_::HashFor<#block_type>>; + + fn map_api_result std::result::Result, R, E>( + &self, + map_call: F, + ) -> std::result::Result where Self: Sized { + map_call(self) + } + + fn has_api( + &self, + _: &#crate_::BlockId<#block_type>, + ) -> std::result::Result where Self: Sized { + Ok(true) + } + + fn has_api_with bool>( + &self, + at: &#crate_::BlockId<#block_type>, + pred: P, + ) -> std::result::Result where Self: Sized { + Ok(pred(A::VERSION)) + } + + fn record_proof(&mut self) { + unimplemented!("`record_proof` not implemented for runtime api mocks") + } + + fn extract_proof(&mut self) -> Option<#crate_::StorageProof> { + unimplemented!("`extract_proof` not implemented for runtime api mocks") + } + + fn into_storage_changes( + &self, + _: &Self::StateBackend, + _: Option<&#crate_::ChangesTrieState< + #crate_::HashFor<#block_type>, + #crate_::NumberFor<#block_type>, + >>, + _: <#block_type as #crate_::BlockT>::Hash, + ) -> std::result::Result< + #crate_::StorageChanges, + String + > where Self: Sized { + unimplemented!("`into_storage_changes` not implemented for runtime api mocks") + } + } + + impl #crate_::Core<#block_type> for #self_ty { + fn Core_version_runtime_api_impl( + &self, + _: &#crate_::BlockId<#block_type>, + _: #crate_::ExecutionContext, + _: Option<()>, + _: Vec, + ) -> std::result::Result<#crate_::NativeOrEncoded<#crate_::RuntimeVersion>, #error_type> { + unimplemented!("Not required for testing!") + } + + fn Core_execute_block_runtime_api_impl( + &self, + _: &#crate_::BlockId<#block_type>, + _: #crate_::ExecutionContext, + _: Option<#block_type>, + _: Vec, + ) -> std::result::Result<#crate_::NativeOrEncoded<()>, #error_type> { + unimplemented!("Not required for testing!") + } + + fn Core_initialize_block_runtime_api_impl( + &self, + _: &#crate_::BlockId<#block_type>, + _: #crate_::ExecutionContext, + _: Option<&<#block_type as #crate_::BlockT>::Header>, + _: Vec, + ) -> std::result::Result<#crate_::NativeOrEncoded<()>, #error_type> { + unimplemented!("Not required for testing!") + } + } + )) +} + +/// Auxialiry 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, + /// Stores the error type that is being found in the trait implementation as associated type + /// with the name `Error`. + error_type: &'a mut Option, +} + +impl<'a> Fold for FoldRuntimeApiImpl<'a> { + fn fold_impl_item_method(&mut self, mut input: syn::ImplItemMethod) -> syn::ImplItemMethod { + let block = { + let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); + + let (param_names, param_types, error) = match extract_parameter_names_types_and_borrows( + &input.sig, + AllowSelfRefInParameters::YesButIgnore, + ) { + Ok(res) => ( + res.iter().map(|v| v.0.clone()).collect::>(), + res.iter().map(|v| { + let ty = &v.1; + let borrow = &v.2; + quote!( #borrow #ty ) + }).collect::>(), + None + ), + Err(e) => (Vec::new(), Vec::new(), Some(e.to_compile_error())), + }; + + let block_type = &self.block_type; + + // Rewrite the input parameters. + input.sig.inputs = parse_quote! { + &self, + _: &#crate_::BlockId<#block_type>, + _: #crate_::ExecutionContext, + params: Option<( #( #param_types ),* )>, + _: Vec, + }; + + 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>, Self::Error> + ); + + let orig_block = input.block.clone(); + + // Generate the new method implementation that calls into the runtime. + parse_quote!( + { + // Get the error to the user (if we have one). + #error + + let (#( #param_names ),*) = params + .expect("Mocked runtime apis don't support calling deprecated api versions"); + + let __fn_implementation__ = move || #orig_block; + + Ok(#crate_::NativeOrEncoded::Native(__fn_implementation__())) + } + ) + }; + + 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 + } + + fn fold_impl_item(&mut self, input: ImplItem) -> ImplItem { + match input { + ImplItem::Type(ty) => { + if ty.ident == "Error" { + if let Some(error_type) = self.error_type { + if *error_type != ty.ty { + let error = Error::new( + ty.span(), + "Error type can not change between runtime apis", + ); + ImplItem::Verbatim(error.to_compile_error()) + } else { + ImplItem::Verbatim(Default::default()) + } + } else { + *self.error_type = Some(ty.ty); + ImplItem::Verbatim(Default::default()) + } + } else { + let error = Error::new( + ty.span(), + "Only associated type with name `Error` is allowed", + ); + ImplItem::Verbatim(error.to_compile_error()) + } + }, + o => fold::fold_impl_item(self, o), + } + } +} + +/// Result of [`generate_runtime_api_impls`]. +struct GeneratedRuntimeApiImpls { + /// All the runtime api implementations. + impls: TokenStream, + /// The error type that should be used by the runtime apis. + error_type: Option, + /// The block type that is being used by the runtime apis. + block_type: TypePath, + /// The type the traits are implemented for. + self_ty: Type, +} + +/// Generate the runtime api implementations from the given trait implementations. +/// +/// This folds the method names, changes the method parameters, method return type, +/// extracts the error type, self type and the block type. +fn generate_runtime_api_impls(impls: &[ItemImpl]) -> Result { + let mut result = Vec::with_capacity(impls.len()); + let mut error_type = None; + let mut global_block_type: Option = None; + let mut self_ty: Option> = 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 block_type = extract_block_type_from_trait_path(impl_trait_path)?; + + self_ty = match self_ty.take() { + Some(self_ty) => { + if self_ty == impl_.self_ty { + Some(self_ty) + } else { + let mut error =Error::new( + impl_.self_ty.span(), + "Self type should not change between runtime apis", + ); + + error.combine(Error::new( + self_ty.span(), + "First self type found here", + )); + + return Err(error) + } + }, + None => Some(impl_.self_ty.clone()), + }; + + global_block_type = match global_block_type.take() { + Some(global_block_type) => { + if global_block_type == *block_type { + Some(global_block_type) + } else { + let mut error = Error::new( + block_type.span(), + "Block type should be the same between all runtime apis.", + ); + + error.combine(Error::new( + global_block_type.span(), + "First block type found here", + )); + + return Err(error) + } + }, + None => Some(block_type.clone()), + }; + + let mut visitor = FoldRuntimeApiImpl { + block_type, + impl_trait: &impl_trait.ident, + error_type: &mut error_type, + }; + + result.push(visitor.fold_item_impl(impl_.clone())); + } + + Ok(GeneratedRuntimeApiImpls { + impls: quote!( #( #result )* ), + error_type, + block_type: global_block_type.expect("There is a least one runtime api; qed"), + self_ty: *self_ty.expect("There is at least one runtime api; qed"), + }) +} + +/// The implementation of the `mock_impl_runtime_apis!` macro. +pub fn mock_impl_runtime_apis_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + // Parse all impl blocks + let RuntimeApiImpls { impls: api_impls } = parse_macro_input!(input as RuntimeApiImpls); + + mock_impl_runtime_apis_impl_inner(&api_impls).unwrap_or_else(|e| e.to_compile_error()).into() +} + +fn mock_impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result { + let hidden_includes = generate_hidden_includes(HIDDEN_INCLUDES_ID); + let GeneratedRuntimeApiImpls { impls, error_type, block_type, self_ty } = + generate_runtime_api_impls(api_impls)?; + let api_traits = implement_common_api_traits(error_type, block_type, self_ty)?; + + Ok(quote!( + #hidden_includes + + #impls + + #api_traits + )) +} diff --git a/primitives/api/proc-macro/src/utils.rs b/primitives/api/proc-macro/src/utils.rs index 8330624bf26d752679ce22afff35a989168a6379..1a79cf6c1ef5b5f046ac98229b3f4203f46ce45b 100644 --- a/primitives/api/proc-macro/src/utils.rs +++ b/primitives/api/proc-macro/src/utils.rs @@ -18,7 +18,7 @@ use proc_macro2::{TokenStream, Span}; use syn::{ Result, Ident, Signature, parse_quote, Type, Pat, spanned::Spanned, FnArg, Error, token::And, - ImplItem, ReturnType, + ImplItem, ReturnType, PathArguments, Path, GenericArgument, TypePath, ItemImpl, }; use quote::quote; @@ -126,13 +126,21 @@ pub fn generate_unique_pattern(pat: Pat, counter: &mut u32) -> Pat { }, _ => pat, } - } +} + +/// Allow `&self` in parameters of a method. +pub enum AllowSelfRefInParameters { + /// Allows `&self` in parameters, but doesn't return it as part of the parameters. + YesButIgnore, + No, +} /// Extracts the name, the type and `&` or ``(if it is a reference or not) /// for each parameter in the given function signature. -pub fn extract_parameter_names_types_and_borrows(sig: &Signature) - -> Result)>> -{ +pub fn extract_parameter_names_types_and_borrows( + sig: &Signature, + allow_self: AllowSelfRefInParameters, +) -> Result)>> { let mut result = Vec::new(); let mut generated_pattern_counter = 0; for input in sig.inputs.iter() { @@ -145,13 +153,20 @@ pub fn extract_parameter_names_types_and_borrows(sig: &Signature) t => { (t.clone(), None) }, }; - let name = - generate_unique_pattern((*arg.pat).clone(), &mut generated_pattern_counter); + let name = generate_unique_pattern( + (*arg.pat).clone(), + &mut generated_pattern_counter, + ); result.push((name, ty, borrow)); }, - FnArg::Receiver(_) => { + FnArg::Receiver(_) if matches!(allow_self, AllowSelfRefInParameters::No) => { return Err(Error::new(input.span(), "`self` parameter not supported!")) - } + }, + FnArg::Receiver(recv) => { + if recv.mutability.is_some() || recv.reference.is_none() { + return Err(Error::new(recv.span(), "Only `&self` is supported!")) + } + }, } } @@ -199,3 +214,60 @@ pub fn extract_all_signature_types(items: &[ImplItem]) -> Vec { .flatten() .collect() } + +/// Extracts the block type from a trait path. +/// +/// It is expected that the block type is the first type in the generic arguments. +pub fn extract_block_type_from_trait_path(trait_: &Path) -> Result<&TypePath> { + let span = trait_.span(); + let generics = trait_ + .segments + .last() + .ok_or_else(|| Error::new(span, "Empty path not supported"))?; + + match &generics.arguments { + PathArguments::AngleBracketed(ref args) => { + args.args.first().and_then(|v| match v { + GenericArgument::Type(Type::Path(ref block)) => Some(block), + _ => None + }).ok_or_else(|| Error::new(args.span(), "Missing `Block` generic parameter.")) + }, + PathArguments::None => { + let span = trait_.segments.last().as_ref().unwrap().span(); + Err(Error::new(span, "Missing `Block` generic parameter.")) + }, + PathArguments::Parenthesized(_) => { + Err(Error::new(generics.arguments.span(), "Unexpected parentheses in path!")) + }, + } +} + +/// Should a qualified trait path be required? +/// +/// e.g. `path::Trait` is qualified and `Trait` is not. +pub enum RequireQualifiedTraitPath { + Yes, + No, +} + +/// Extract the trait that is implemented by the given `ItemImpl`. +pub fn extract_impl_trait<'a>( + impl_: &'a ItemImpl, + require: RequireQualifiedTraitPath, +) -> Result<&'a Path> { + impl_.trait_.as_ref().map(|v| &v.1).ok_or_else( + || Error::new(impl_.span(), "Only implementation of traits are supported!") + ).and_then(|p| { + if p.segments.len() > 1 || matches!(require, RequireQualifiedTraitPath::No) { + Ok(p) + } else { + Err( + Error::new( + p.span(), + "The implemented trait has to be referenced with a path, \ + e.g. `impl client::Core for Runtime`." + ) + ) + } + }) +} diff --git a/primitives/api/src/lib.rs b/primitives/api/src/lib.rs index 0901be5831dfd9758bdc59b466302963ad8f0fb8..74bcf19a9949e43316001d34b7ccfe3bda2b73cb 100644 --- a/primitives/api/src/lib.rs +++ b/primitives/api/src/lib.rs @@ -36,7 +36,7 @@ extern crate self as sp_api; #[doc(hidden)] #[cfg(feature = "std")] pub use sp_state_machine::{ - OverlayedChanges, StorageProof, Backend as StateBackend, ChangesTrieState, + OverlayedChanges, StorageProof, Backend as StateBackend, ChangesTrieState, InMemoryBackend, }; #[doc(hidden)] #[cfg(feature = "std")] @@ -78,7 +78,8 @@ use std::{panic::UnwindSafe, cell::RefCell}; /// declaration. Besides one exception, the macro adds an extra generic parameter `Block: BlockT` /// to the client side and the runtime side. This generic parameter is usable by the user. /// -/// For implementing these macros you should use the `impl_runtime_apis!` macro. +/// For implementing these macros you should use the +/// [`impl_runtime_apis!`](macro.impl_runtime_apis.html) macro. /// /// # Example /// @@ -143,8 +144,9 @@ pub use sp_api_proc_macro::decl_runtime_apis; /// Tags given trait implementations as runtime apis. /// -/// All traits given to this macro, need to be declared with the `decl_runtime_apis!` macro. -/// The implementation of the trait should follow the declaration given to the `decl_runtime_apis!` +/// All traits given to this macro, need to be declared with the +/// [`decl_runtime_apis!`](macro.decl_runtime_apis.html) macro. The implementation of the trait +/// should follow the declaration given to the [`decl_runtime_apis!`](macro.decl_runtime_apis.html) /// macro, besides the `Block` type that is required as first generic parameter for each runtime /// api trait. When implementing a runtime api trait, it is required that the trait is referenced /// by a path, e.g. `impl my_trait::MyTrait for Runtime`. The macro will use this path to access @@ -182,7 +184,7 @@ pub use sp_api_proc_macro::decl_runtime_apis; /// # } /// # pub trait BlockBuilder { /// # fn build_block() -> Block; -/// # } +/// # } /// # } /// /// /// All runtime api implementations need to be done in one call of the macro! @@ -226,6 +228,70 @@ pub use sp_api_proc_macro::decl_runtime_apis; /// ``` pub use sp_api_proc_macro::impl_runtime_apis; +/// Mocks given trait implementations as runtime apis. +/// +/// Accepts similar syntax as [`impl_runtime_apis!`](macro.impl_runtime_apis.html) and generates +/// simplified mock implementations of the given runtime apis. The difference in syntax is that the +/// trait does not need to be referenced by a qualified path, methods accept the `&self` parameter +/// and the error type can be specified as associated type. If no error type is specified `String` +/// is used as error type. +/// +/// Besides implementing the given traits, the [`Core`], [`ApiExt`] and [`ApiErrorExt`] are +/// implemented automatically. +/// +/// # Example +/// +/// ```rust +/// use sp_version::create_runtime_str; +/// # +/// # use sp_runtime::traits::Block as BlockT; +/// # use sp_test_primitives::Block; +/// # +/// # sp_api::decl_runtime_apis! { +/// # /// Declare the api trait. +/// # pub trait Balance { +/// # /// Get the balance. +/// # fn get_balance() -> u64; +/// # /// Set the balance. +/// # fn set_balance(val: u64); +/// # } +/// # pub trait BlockBuilder { +/// # fn build_block() -> Block; +/// # } +/// # } +/// +/// struct MockApi { +/// balance: u64, +/// } +/// +/// /// All runtime api mock implementations need to be done in one call of the macro! +/// sp_api::mock_impl_runtime_apis! { +/// impl Balance for MockApi { +/// /// Here we take the `&self` to access the instance. +/// fn get_balance(&self) -> u64 { +/// self.balance +/// } +/// fn set_balance(_bal: u64) { +/// // Store the balance +/// } +/// } +/// +/// impl BlockBuilder for MockApi { +/// /// Sets the error type that is being used by the mock implementation. +/// /// The error type is used by all runtime apis. It is only required to +/// /// be specified in one trait implementation. +/// type Error = String; +/// +/// fn build_block() -> Block { +/// unimplemented!("Not Required in tests") +/// } +/// } +/// } +/// +/// # fn main() {} +/// ``` +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>; @@ -293,21 +359,14 @@ pub trait ApiExt: ApiErrorExt { fn has_api( &self, at: &BlockId, - ) -> Result where Self: Sized { - self.runtime_version_at(at).map(|v| v.has_api_with(&A::ID, |v| v == A::VERSION)) - } + ) -> Result where Self: Sized; /// Check if the given api is implemented and the version passes a predicate. fn has_api_with bool>( &self, at: &BlockId, pred: P, - ) -> Result where Self: Sized { - self.runtime_version_at(at).map(|v| v.has_api_with(&A::ID, pred)) - } - - /// Returns the runtime version at the given block id. - fn runtime_version_at(&self, at: &BlockId) -> Result; + ) -> Result where Self: Sized; /// Start recording all accessed trie nodes for generating proofs. fn record_proof(&mut self); diff --git a/primitives/api/test/Cargo.toml b/primitives/api/test/Cargo.toml index 3b41e28cf3b2b70e489835c605880a019e193c25..f2e66afc24abbac9f4c5604701d6b6f558bf3b4d 100644 --- a/primitives/api/test/Cargo.toml +++ b/primitives/api/test/Cargo.toml @@ -9,22 +9,22 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-api = { version = "2.0.0-alpha.2", path = "../" } +sp-api = { version = "2.0.0-alpha.5", path = "../" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../../test-utils/runtime/client" } -sp-version = { version = "2.0.0-alpha.2", path = "../../version" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../runtime" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../blockchain" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../../primitives/consensus/common" } -sc-block-builder = { version = "0.8.0-alpha.2", path = "../../../client/block-builder" } -codec = { package = "parity-scale-codec", version = "1.2.0" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../../primitives/state-machine" } +sp-version = { version = "2.0.0-alpha.5", path = "../../version" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../runtime" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../blockchain" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../../primitives/consensus/common" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "../../../client/block-builder" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../../primitives/state-machine" } trybuild = "1.0.17" rustversion = "1.0.0" [dev-dependencies] criterion = "0.3.0" substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../../test-utils/runtime/client" } -sp-core = { version = "2.0.0-alpha.1", path = "../../core" } +sp-core = { version = "2.0.0-alpha.5", path = "../../core" } [[bench]] name = "bench" @@ -34,3 +34,6 @@ harness = false [features] default = [ "std" ] std = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/api/test/tests/decl_and_impl.rs b/primitives/api/test/tests/decl_and_impl.rs index d5a668dec1245a34ad054bfd7813298863aa07ca..a09bd0412c8afe9f15deb078a3f338dd0937ac59 100644 --- a/primitives/api/test/tests/decl_and_impl.rs +++ b/primitives/api/test/tests/decl_and_impl.rs @@ -14,7 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use sp_api::{RuntimeApiInfo, decl_runtime_apis, impl_runtime_apis}; +use sp_api::{ + RuntimeApiInfo, decl_runtime_apis, impl_runtime_apis, mock_impl_runtime_apis, + ApiExt, +}; use sp_runtime::{traits::{GetNodeBlockType, Block as BlockT}, generic::BlockId}; @@ -81,6 +84,34 @@ impl_runtime_apis! { } } +struct MockApi { + block: Option, +} + +mock_impl_runtime_apis! { + impl Api for MockApi { + fn test(_: u64) { + unimplemented!() + } + + fn something_with_block(&self, _: Block) -> Block { + self.block.clone().unwrap() + } + + fn function_with_two_args(_: u64, _: Block) { + unimplemented!() + } + + fn same_name() {} + + fn wild_card(_: u32) {} + } + + impl ApiWithCustomVersion for MockApi { + fn same_name() {} + } +} + type TestClient = substrate_test_runtime_client::sc_client::Client< substrate_test_runtime_client::Backend, substrate_test_runtime_client::Executor, @@ -129,3 +160,22 @@ fn check_runtime_api_versions() { check_runtime_api_versions_contains::>(); check_runtime_api_versions_contains::>(); } + +#[test] +fn mock_runtime_api_has_api() { + let mock = MockApi { block: None }; + + assert!( + mock.has_api::>(&BlockId::Number(0)).unwrap(), + ); + assert!(mock.has_api::>(&BlockId::Number(0)).unwrap()); +} + +#[test] +#[should_panic(expected = "Mocked runtime apis don't support calling deprecated api versions")] +fn mock_runtime_api_panics_on_calling_old_version() { + let mock = MockApi { block: None }; + + #[allow(deprecated)] + let _ = mock.same_name_before_version_2(&BlockId::Number(0)); +} diff --git a/primitives/api/test/tests/ui/mock_only_error_associated_type.rs b/primitives/api/test/tests/ui/mock_only_error_associated_type.rs new file mode 100644 index 0000000000000000000000000000000000000000..bbd3c71c940177aba081954af7291c4f427bf604 --- /dev/null +++ b/primitives/api/test/tests/ui/mock_only_error_associated_type.rs @@ -0,0 +1,19 @@ +use substrate_test_runtime_client::runtime::Block; + +sp_api::decl_runtime_apis! { + pub trait Api { + fn test(data: u64); + } +} + +struct MockApi; + +sp_api::mock_impl_runtime_apis! { + impl Api for MockApi { + type OtherData = u32; + + fn test(data: u64) {} + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/mock_only_error_associated_type.stderr b/primitives/api/test/tests/ui/mock_only_error_associated_type.stderr new file mode 100644 index 0000000000000000000000000000000000000000..beced70413bb07cb873789ba48181d0d59d6ac32 --- /dev/null +++ b/primitives/api/test/tests/ui/mock_only_error_associated_type.stderr @@ -0,0 +1,5 @@ +error: Only associated type with name `Error` is allowed + --> $DIR/mock_only_error_associated_type.rs:13:3 + | +13 | type OtherData = u32; + | ^^^^ diff --git a/primitives/api/test/tests/ui/mock_only_one_block_type.rs b/primitives/api/test/tests/ui/mock_only_one_block_type.rs new file mode 100644 index 0000000000000000000000000000000000000000..969b21d7378d75b6b2bb28c1ca118aa041015784 --- /dev/null +++ b/primitives/api/test/tests/ui/mock_only_one_block_type.rs @@ -0,0 +1,27 @@ +use substrate_test_runtime_client::runtime::Block; + +struct Block2; + +sp_api::decl_runtime_apis! { + pub trait Api { + fn test(data: u64); + } + + pub trait Api2 { + fn test(data: u64); + } +} + +struct MockApi; + +sp_api::mock_impl_runtime_apis! { + impl Api for MockApi { + fn test(data: u64) {} + } + + impl Api2 for MockApi { + fn test(data: u64) {} + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/mock_only_one_block_type.stderr b/primitives/api/test/tests/ui/mock_only_one_block_type.stderr new file mode 100644 index 0000000000000000000000000000000000000000..1abc8db726a160e151cb47abf6b745d5630b1851 --- /dev/null +++ b/primitives/api/test/tests/ui/mock_only_one_block_type.stderr @@ -0,0 +1,19 @@ +error: Block type should be the same between all runtime apis. + --> $DIR/mock_only_one_block_type.rs:22:12 + | +22 | impl Api2 for MockApi { + | ^^^^^^ + +error: First block type found here + --> $DIR/mock_only_one_block_type.rs:18:11 + | +18 | impl Api for MockApi { + | ^^^^^ + +warning: unused import: `substrate_test_runtime_client::runtime::Block` + --> $DIR/mock_only_one_block_type.rs:1:5 + | +1 | use substrate_test_runtime_client::runtime::Block; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default diff --git a/primitives/api/test/tests/ui/mock_only_one_error_type.rs b/primitives/api/test/tests/ui/mock_only_one_error_type.rs new file mode 100644 index 0000000000000000000000000000000000000000..1c3f13dbb9bf1107e5c88fa50bd6ef12eb485b4c --- /dev/null +++ b/primitives/api/test/tests/ui/mock_only_one_error_type.rs @@ -0,0 +1,29 @@ +use substrate_test_runtime_client::runtime::Block; + +sp_api::decl_runtime_apis! { + pub trait Api { + fn test(data: u64); + } + + pub trait Api2 { + fn test(data: u64); + } +} + +struct MockApi; + +sp_api::mock_impl_runtime_apis! { + impl Api for MockApi { + type Error = u32; + + fn test(data: u64) {} + } + + impl Api2 for MockApi { + type Error = u64; + + fn test(data: u64) {} + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/mock_only_one_error_type.stderr b/primitives/api/test/tests/ui/mock_only_one_error_type.stderr new file mode 100644 index 0000000000000000000000000000000000000000..8f026838c96b8db0e503179c745b3a76f4a2c181 --- /dev/null +++ b/primitives/api/test/tests/ui/mock_only_one_error_type.stderr @@ -0,0 +1,27 @@ +error: Error type can not change between runtime apis + --> $DIR/mock_only_one_error_type.rs:23:3 + | +23 | type Error = u64; + | ^^^^ + +error[E0277]: the trait bound `u32: std::convert::From` is not satisfied + --> $DIR/mock_only_one_error_type.rs:15:1 + | +15 | / sp_api::mock_impl_runtime_apis! { +16 | | impl Api for MockApi { +17 | | type Error = u32; +18 | | +... | +26 | | } +27 | | } + | | ^ + | | | + | |_the trait `std::convert::From` is not implemented for `u32` + | in this macro invocation + | + = help: the following implementations were found: + > + > + > + > + and 16 others diff --git a/primitives/api/test/tests/ui/mock_only_one_self_type.rs b/primitives/api/test/tests/ui/mock_only_one_self_type.rs new file mode 100644 index 0000000000000000000000000000000000000000..4b29ec2a6ab07e7a08689549423e03687501543d --- /dev/null +++ b/primitives/api/test/tests/ui/mock_only_one_self_type.rs @@ -0,0 +1,26 @@ +use substrate_test_runtime_client::runtime::Block; + +sp_api::decl_runtime_apis! { + pub trait Api { + fn test(data: u64); + } + + pub trait Api2 { + fn test(data: u64); + } +} + +struct MockApi; +struct MockApi2; + +sp_api::mock_impl_runtime_apis! { + impl Api for MockApi { + fn test(data: u64) {} + } + + impl Api2 for MockApi2 { + fn test(data: u64) {} + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/mock_only_one_self_type.stderr b/primitives/api/test/tests/ui/mock_only_one_self_type.stderr new file mode 100644 index 0000000000000000000000000000000000000000..996d1d44c044c151bc5e57e93e83d3749e86a80c --- /dev/null +++ b/primitives/api/test/tests/ui/mock_only_one_self_type.stderr @@ -0,0 +1,19 @@ +error: Self type should not change between runtime apis + --> $DIR/mock_only_one_self_type.rs:21:23 + | +21 | impl Api2 for MockApi2 { + | ^^^^^^^^ + +error: First self type found here + --> $DIR/mock_only_one_self_type.rs:17:22 + | +17 | impl Api for MockApi { + | ^^^^^^^ + +warning: unused import: `substrate_test_runtime_client::runtime::Block` + --> $DIR/mock_only_one_self_type.rs:1:5 + | +1 | use substrate_test_runtime_client::runtime::Block; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default diff --git a/primitives/api/test/tests/ui/mock_only_self_reference.rs b/primitives/api/test/tests/ui/mock_only_self_reference.rs new file mode 100644 index 0000000000000000000000000000000000000000..8a733f5779ce938edcd1301b535938288b66737b --- /dev/null +++ b/primitives/api/test/tests/ui/mock_only_self_reference.rs @@ -0,0 +1,20 @@ +use substrate_test_runtime_client::runtime::Block; + +sp_api::decl_runtime_apis! { + pub trait Api { + fn test(data: u64); + fn test2(data: u64); + } +} + +struct MockApi; + +sp_api::mock_impl_runtime_apis! { + impl Api for MockApi { + fn test(self, data: u64) {} + + fn test2(&mut self, data: u64) {} + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/mock_only_self_reference.stderr b/primitives/api/test/tests/ui/mock_only_self_reference.stderr new file mode 100644 index 0000000000000000000000000000000000000000..9c1658b0a6cea1260b082eee7afea6d2521c66ac --- /dev/null +++ b/primitives/api/test/tests/ui/mock_only_self_reference.stderr @@ -0,0 +1,65 @@ +error: Only `&self` is supported! + --> $DIR/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 + | +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 + | +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 +... +12 | sp_api::mock_impl_runtime_apis! { + | -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | _expected `u64`, found `()` + | | +13 | | impl Api for MockApi { +14 | | fn test(self, data: u64) {} +15 | | +16 | | fn test2(&mut self, data: u64) {} +17 | | } +18 | | } + | |_- in this macro invocation + | + = note: expected fn pointer `fn(&MockApi, &sp_api_hidden_includes_DECL_RUNTIME_APIS::sp_api::BlockId, substrate_test_runtime::Extrinsic>>, sp_api_hidden_includes_DECL_RUNTIME_APIS::sp_api::ExecutionContext, std::option::Option, std::vec::Vec<_>) -> std::result::Result<_, _>` + found fn pointer `fn(&MockApi, &sp_api_hidden_includes_DECL_RUNTIME_APIS::sp_api::BlockId, substrate_test_runtime::Extrinsic>>, sp_api_hidden_includes_DECL_RUNTIME_APIS::sp_api::ExecutionContext, std::option::Option<()>, std::vec::Vec<_>) -> std::result::Result<_, _>` + +error[E0053]: method `Api_test2_runtime_api_impl` has an incompatible type for trait + --> $DIR/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 +... +12 | sp_api::mock_impl_runtime_apis! { + | -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | _expected `u64`, found `()` + | | +13 | | impl Api for MockApi { +14 | | fn test(self, data: u64) {} +15 | | +16 | | fn test2(&mut self, data: u64) {} +17 | | } +18 | | } + | |_- in this macro invocation + | + = note: expected fn pointer `fn(&MockApi, &sp_api_hidden_includes_DECL_RUNTIME_APIS::sp_api::BlockId, substrate_test_runtime::Extrinsic>>, sp_api_hidden_includes_DECL_RUNTIME_APIS::sp_api::ExecutionContext, std::option::Option, std::vec::Vec<_>) -> std::result::Result<_, _>` + found fn pointer `fn(&MockApi, &sp_api_hidden_includes_DECL_RUNTIME_APIS::sp_api::BlockId, substrate_test_runtime::Extrinsic>>, sp_api_hidden_includes_DECL_RUNTIME_APIS::sp_api::ExecutionContext, std::option::Option<()>, std::vec::Vec<_>) -> std::result::Result<_, _>` diff --git a/primitives/application-crypto/Cargo.toml b/primitives/application-crypto/Cargo.toml index ee710b29885a3fbe80202eb06e5799d875614cda..7c3fb5357997ffa2e7470c5aaf94cca168594fc8 100644 --- a/primitives/application-crypto/Cargo.toml +++ b/primitives/application-crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-application-crypto" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" description = "Provides facilities for generating application specific crypto wrapper types." @@ -11,11 +11,11 @@ documentation = "https://docs.rs/sp-application-crypto" [dependencies] -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../core" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../core" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } serde = { version = "1.0.101", optional = true, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } [features] default = [ "std" ] @@ -31,3 +31,6 @@ full_crypto = [ "sp-io/disable_panic_handler", "sp-io/disable_oom", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/application-crypto/src/ed25519.rs b/primitives/application-crypto/src/ed25519.rs index 414715a10684c18b49a33fe6bb1fc08c20639d4a..5be79ff4f7985a54e9eb0c34fb3552f3cf062bb1 100644 --- a/primitives/application-crypto/src/ed25519.rs +++ b/primitives/application-crypto/src/ed25519.rs @@ -23,12 +23,27 @@ use sp_std::vec::Vec; pub use sp_core::ed25519::*; mod app { + use sp_core::crypto::{CryptoTypePublicPair, Public as TraitPublic}; use sp_core::testing::ED25519; + use sp_core::ed25519::CRYPTO_ID; + crate::app_crypto!(super, ED25519); impl crate::traits::BoundToRuntimeAppPublic for Public { type Public = Self; } + + impl From for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } + } + + impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) + } + } } pub use app::{Public as AppPublic, Signature as AppSignature}; diff --git a/primitives/application-crypto/src/lib.rs b/primitives/application-crypto/src/lib.rs index b7c9ccaa9821aa49b9f14e70f841e05e1ef9853f..07e2b45106ee9690c841a5d842eebdc4325afd59 100644 --- a/primitives/application-crypto/src/lib.rs +++ b/primitives/application-crypto/src/lib.rs @@ -21,11 +21,11 @@ #![cfg_attr(not(feature = "std"), no_std)] #[doc(hidden)] -pub use sp_core::{self, crypto::{CryptoType, Public, Derive, IsWrappedBy, Wraps}, RuntimeDebug}; +pub use sp_core::{self, crypto::{CryptoType, CryptoTypePublicPair, Public, Derive, IsWrappedBy, Wraps}, RuntimeDebug}; #[doc(hidden)] #[cfg(feature = "full_crypto")] pub use sp_core::crypto::{SecretStringError, DeriveJunction, Ss58Codec, Pair}; -pub use sp_core::{crypto::{KeyTypeId, key_types}}; +pub use sp_core::crypto::{CryptoTypeId, KeyTypeId, key_types}; #[doc(hidden)] pub use codec; diff --git a/primitives/application-crypto/src/sr25519.rs b/primitives/application-crypto/src/sr25519.rs index 59c6f19b6f2668939054127347f5a87ed6989907..a0f2cef1c4e45b6150be335ac271e3dd592febbe 100644 --- a/primitives/application-crypto/src/sr25519.rs +++ b/primitives/application-crypto/src/sr25519.rs @@ -23,12 +23,27 @@ use sp_std::vec::Vec; pub use sp_core::sr25519::*; mod app { + use sp_core::crypto::{CryptoTypePublicPair, Public as TraitPublic}; use sp_core::testing::SR25519; + use sp_core::sr25519::CRYPTO_ID; + crate::app_crypto!(super, SR25519); impl crate::traits::BoundToRuntimeAppPublic for Public { type Public = Self; } + + impl From for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } + } + + impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) + } + } } pub use app::{Public as AppPublic, Signature as AppSignature}; diff --git a/primitives/application-crypto/test/Cargo.toml b/primitives/application-crypto/test/Cargo.toml index 0ed5355ce2d1e527303aa2badb0f9a1f1c544200..d34840b4eb5206429fbf829f60c2e11187b838f4 100644 --- a/primitives/application-crypto/test/Cargo.toml +++ b/primitives/application-crypto/test/Cargo.toml @@ -10,8 +10,11 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../core" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../core" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../../test-utils/runtime/client" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../runtime" } -sp-api = { version = "2.0.0-alpha.2", path = "../../api" } -sp-application-crypto = { version = "2.0.0-alpha.2", path = "../" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../runtime" } +sp-api = { version = "2.0.0-alpha.5", path = "../../api" } +sp-application-crypto = { version = "2.0.0-alpha.5", path = "../" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/application-crypto/test/src/ed25519.rs b/primitives/application-crypto/test/src/ed25519.rs index 400edfd6ae4402f9ab77fc0fe4fde9f61dfd89ce..1d72962829a53309ed4146a5f8657820ba15b697 100644 --- a/primitives/application-crypto/test/src/ed25519.rs +++ b/primitives/application-crypto/test/src/ed25519.rs @@ -17,7 +17,10 @@ //! Integration tests for ed25519 use sp_runtime::generic::BlockId; -use sp_core::{testing::{KeyStore, ED25519}, crypto::Pair}; +use sp_core::{ + crypto::Pair, + testing::{KeyStore, ED25519}, +}; use substrate_test_runtime_client::{ TestClientBuilder, DefaultTestClientBuilderExt, TestClientBuilderExt, runtime::TestAPI, @@ -33,8 +36,7 @@ fn ed25519_works_in_runtime() { .test_ed25519_crypto(&BlockId::Number(0)) .expect("Tests `ed25519` crypto."); - let key_pair = keystore.read().ed25519_key_pair(ED25519, &public.as_ref()) - .expect("There should be at a `ed25519` key in the keystore for the given public key."); - - assert!(AppPair::verify(&signature, "ed25519", &AppPublic::from(key_pair.public()))); + let supported_keys = keystore.read().keys(ED25519).unwrap(); + assert!(supported_keys.contains(&public.clone().into())); + assert!(AppPair::verify(&signature, "ed25519", &AppPublic::from(public))); } diff --git a/primitives/application-crypto/test/src/sr25519.rs b/primitives/application-crypto/test/src/sr25519.rs index 49bb3c2a8364027c1dd4eceef607deb49ca09469..f2c7c48b2bc91c0329ecaad7541be5f13f3f754c 100644 --- a/primitives/application-crypto/test/src/sr25519.rs +++ b/primitives/application-crypto/test/src/sr25519.rs @@ -18,7 +18,10 @@ use sp_runtime::generic::BlockId; -use sp_core::{testing::{KeyStore, SR25519}, crypto::Pair}; +use sp_core::{ + crypto::Pair, + testing::{KeyStore, SR25519}, +}; use substrate_test_runtime_client::{ TestClientBuilder, DefaultTestClientBuilderExt, TestClientBuilderExt, runtime::TestAPI, @@ -34,8 +37,7 @@ fn sr25519_works_in_runtime() { .test_sr25519_crypto(&BlockId::Number(0)) .expect("Tests `sr25519` crypto."); - let key_pair = keystore.read().sr25519_key_pair(SR25519, public.as_ref()) - .expect("There should be at a `sr25519` key in the keystore for the given public key."); - - assert!(AppPair::verify(&signature, "sr25519", &AppPublic::from(key_pair.public()))); + let supported_keys = keystore.read().keys(SR25519).unwrap(); + assert!(supported_keys.contains(&public.clone().into())); + assert!(AppPair::verify(&signature, "sr25519", &AppPublic::from(public))); } diff --git a/primitives/arithmetic/Cargo.toml b/primitives/arithmetic/Cargo.toml index 617e8b8e5ceb86866e51eed4bfa036bcae7d2c61..208525f6c193bdca2b9b5f9ba4a566999d523544 100644 --- a/primitives/arithmetic/Cargo.toml +++ b/primitives/arithmetic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-arithmetic" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,15 +11,15 @@ documentation = "https://docs.rs/sp-arithmetic" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } integer-sqrt = "0.1.2" num-traits = { version = "0.2.8", default-features = false } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } serde = { version = "1.0.101", optional = true, features = ["derive"] } -sp-debug-derive = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/debug-derive" } +sp-debug-derive = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/debug-derive" } [dev-dependencies] -primitive-types = "0.6.2" +primitive-types = "0.7.0" rand = "0.7.2" criterion = "0.3" @@ -36,3 +36,6 @@ std = [ [[bench]] name = "bench" harness = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/arithmetic/fuzzer/Cargo.lock b/primitives/arithmetic/fuzzer/Cargo.lock index b196418375c59ee17bb8ba03680011d5b0efd1da..3a4187437ae7312ec7016cb9e41638a018cfcbbf 100644 --- a/primitives/arithmetic/fuzzer/Cargo.lock +++ b/primitives/arithmetic/fuzzer/Cargo.lock @@ -32,9 +32,9 @@ checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" [[package]] name = "byteorder" -version = "1.3.2" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "c2-chacha" @@ -83,8 +83,6 @@ dependencies = [ [[package]] name = "honggfuzz" version = "0.5.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c27b4aa3049d6d10d8e33d52c9d03ca9aec18f8a449b246f8c4a5b0c10fb34" dependencies = [ "arbitrary", "lazy_static", @@ -114,9 +112,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.66" +version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" [[package]] name = "memmap" @@ -160,9 +158,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "1.1.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f747c06d9f3b2ad387ac881b9667298c81b1243aa9833f086e05996937c35507" +checksum = "f509c5e67ca0605ee17dcd3f91ef41cadd685c75a298fb6261b781a5acb3f910" dependencies = [ "arrayvec", "bitvec", @@ -173,9 +171,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "1.1.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34e513ff3e406f3ede6796dcdc83d0b32ffb86668cea1ccf7363118abeb00476" +checksum = "5a0ec292e92e8ec7c58e576adacc1e3f399c597c8f263c42f18420abe58e7245" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -211,18 +209,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" +checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" dependencies = [ "proc-macro2", ] @@ -296,7 +294,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" -version = "2.0.0" +version = "2.0.0-alpha.3" dependencies = [ "integer-sqrt", "num-traits", @@ -319,7 +317,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" -version = "2.0.0" +version = "2.0.0-alpha.3" dependencies = [ "proc-macro2", "quote", @@ -328,7 +326,7 @@ dependencies = [ [[package]] name = "sp-std" -version = "2.0.0" +version = "2.0.0-alpha.3" [[package]] name = "static_assertions" @@ -338,9 +336,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" +checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" dependencies = [ "proc-macro2", "quote", diff --git a/primitives/arithmetic/fuzzer/Cargo.toml b/primitives/arithmetic/fuzzer/Cargo.toml index 19d677f744502f1fdceb72920cf0d35a97760427..26b5f8c27b5085fd615dce5af70939e994c76767 100644 --- a/primitives/arithmetic/fuzzer/Cargo.toml +++ b/primitives/arithmetic/fuzzer/Cargo.toml @@ -1,23 +1,32 @@ [package] name = "sp-arithmetic-fuzzer" -version = "2.0.0" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "Fuzzer for fixed point arithmetic primitives." +documentation = "https://docs.rs/sp-arithmetic-fuzzer" [dependencies] -sp-arithmetic = { version = "2.0.0", path = ".." } +sp-arithmetic = { version = "2.0.0-alpha.5", path = ".." } honggfuzz = "0.5" -primitive-types = "0.6.2" +primitive-types = "0.7.0" num-bigint = "0.2" num-traits = "0.2" -[workspace] - [[bin]] name = "biguint" path = "src/biguint.rs" +[[bin]] +name = "per_thing_rational" +path = "src/per_thing_rational.rs" + [[bin]] name = "rational128" path = "src/rational128.rs" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/arithmetic/fuzzer/src/per_thing_rational.rs b/primitives/arithmetic/fuzzer/src/per_thing_rational.rs new file mode 100644 index 0000000000000000000000000000000000000000..c2dda3de2299cb713114a856c6eafbedf2d91be2 --- /dev/null +++ b/primitives/arithmetic/fuzzer/src/per_thing_rational.rs @@ -0,0 +1,122 @@ +// Copyright 2019-2020 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 . + +//! # Running +//! Running this fuzzer can be done with `cargo hfuzz run per_thing_rational`. `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/per_thing_rational/*.fuzz`. + +use honggfuzz::fuzz; +use sp_arithmetic::{ + PerThing, PerU16, Percent, Perbill, Perquintill, traits::SaturatedConversion, +}; + +fn main() { + loop { + fuzz!(| + data: ((u16, u16), (u32, u32), (u64, u64)) + | { + + let (u16_pair, u32_pair, u64_pair) = data; + + // peru16 + let (smaller, bigger) = (u16_pair.0.min(u16_pair.1), u16_pair.0.max(u16_pair.1)); + let ratio = PerU16::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + PerU16::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1)); + let ratio = PerU16::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + PerU16::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); + let ratio = PerU16::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + PerU16::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + + // percent + let (smaller, bigger) = (u16_pair.0.min(u16_pair.1), u16_pair.0.max(u16_pair.1)); + let ratio = Percent::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Percent::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + + let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1)); + let ratio = Percent::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Percent::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + + let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); + let ratio = Percent::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Percent::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + + // perbill + let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1)); + let ratio = Perbill::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Perbill::from_fraction(smaller as f64 / bigger.max(1) as f64), + 100, + ); + + let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); + let ratio = Perbill::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Perbill::from_fraction(smaller as f64 / bigger.max(1) as f64), + 100, + ); + + // perquintillion + let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); + let ratio = Perquintill::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Perquintill::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1000, + ); + + }) + } +} + +fn assert_per_thing_equal_error(a: T, b: T, err: u128) { + let a_abs = a.deconstruct().saturated_into::(); + let b_abs = b.deconstruct().saturated_into::(); + let diff = a_abs.max(b_abs) - a_abs.min(b_abs); + dbg!(&diff); + assert!(diff <= err, "{:?} !~ {:?}", a, b); +} diff --git a/primitives/arithmetic/src/fixed64.rs b/primitives/arithmetic/src/fixed64.rs index eea1ab68a3bb2272714fb0e3e506201634ffcaa0..6b399b6aa5106d3889c1152c8cb1e7fc38a14585 100644 --- a/primitives/arithmetic/src/fixed64.rs +++ b/primitives/arithmetic/src/fixed64.rs @@ -110,12 +110,18 @@ impl Saturating for Fixed64 { fn saturating_add(self, rhs: Self) -> Self { Self(self.0.saturating_add(rhs.0)) } + fn saturating_mul(self, rhs: Self) -> Self { Self(self.0.saturating_mul(rhs.0) / DIV) } + fn saturating_sub(self, rhs: Self) -> Self { Self(self.0.saturating_sub(rhs.0)) } + + fn saturating_pow(self, exp: usize) -> Self { + Self(self.0.saturating_pow(exp as u32)) + } } /// Note that this is a standard, _potentially-panicking_, implementation. Use `Saturating` trait diff --git a/primitives/arithmetic/src/lib.rs b/primitives/arithmetic/src/lib.rs index c2feae00b74ff9a3e0d016558c2dfbaee2e1dffd..f6d8b53e3499b340b27285964e8c366be39ca158 100644 --- a/primitives/arithmetic/src/lib.rs +++ b/primitives/arithmetic/src/lib.rs @@ -19,7 +19,7 @@ #![cfg_attr(not(feature = "std"), no_std)] /// Copied from `sp-runtime` and documented there. -#[cfg(test)] +#[macro_export] macro_rules! assert_eq_error_rate { ($x:expr, $y:expr, $error:expr $(,)?) => { assert!( @@ -40,5 +40,17 @@ mod fixed64; mod rational128; pub use fixed64::Fixed64; -pub use per_things::{PerThing, Percent, Permill, Perbill, Perquintill}; +pub use per_things::{PerThing, Percent, PerU16, Permill, Perbill, Perquintill}; pub use rational128::Rational128; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn peru16_rational_does_not_overflow() { + // A historical example that will panic only for per_thing type that are created with + // maximum capacity of their type, e.g. PerU16. + let _ = PerU16::from_rational_approximation(17424870u32, 17424870); + } +} diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index cbb804baf5d49e97ce7d9817b7169dffe6e87643..ad529fbf32e249ef1fb0e5838c30fce52aab4b25 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -17,42 +17,160 @@ #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; -use sp_std::{ops, prelude::*, convert::TryInto}; -use codec::{Encode, Decode, CompactAs}; +use sp_std::{ops, fmt, prelude::*, convert::TryInto}; +use codec::{Encode, CompactAs}; use crate::traits::{ - SaturatedConversion, UniqueSaturatedInto, Saturating, BaseArithmetic, + SaturatedConversion, UniqueSaturatedInto, Saturating, BaseArithmetic, Bounded, Zero, }; use sp_debug_derive::RuntimeDebug; /// Something that implements a fixed point ration with an arbitrary granularity `X`, as _parts per /// `X`_. -pub trait PerThing: Sized + Saturating + Copy { +pub trait PerThing: + Sized + Saturating + Copy + Default + Eq + PartialEq + Ord + PartialOrd + Bounded + fmt::Debug +{ /// The data type used to build this per-thingy. - type Inner: BaseArithmetic + Copy; + type Inner: BaseArithmetic + Copy + fmt::Debug; - /// accuracy of this type - const ACCURACY: Self::Inner; + /// A data type larger than `Self::Inner`, used to avoid overflow in some computations. + /// It must be able to compute `ACCURACY^2`. + type Upper: BaseArithmetic + Copy + From + TryInto + fmt::Debug; - /// NoThing - fn zero() -> Self; + /// The accuracy of this type. + const ACCURACY: Self::Inner; - /// `true` if this is nothing. - fn is_zero(&self) -> bool; + /// Equivalent to `Self::from_parts(0)`. + fn zero() -> Self { Self::from_parts(Self::Inner::zero()) } - /// Everything. - fn one() -> Self; + /// Return `true` if this is nothing. + fn is_zero(&self) -> bool { self.deconstruct() == Self::Inner::zero() } - /// Consume self and deconstruct into a raw numeric type. - fn deconstruct(self) -> Self::Inner; + /// Equivalent to `Self::from_parts(Self::ACCURACY)`. + fn one() -> Self { Self::from_parts(Self::ACCURACY) } - /// From an explicitly defined number of parts per maximum of the type. - fn from_parts(parts: Self::Inner) -> Self; + /// Return `true` if this is one. + fn is_one(&self) -> bool { self.deconstruct() == Self::ACCURACY } - /// Converts a percent into `Self`. Equal to `x / 100`. - fn from_percent(x: Self::Inner) -> Self; + /// Build this type from a percent. Equivalent to `Self::from_parts(x * Self::ACCURACY / 100)` + /// but more accurate. + fn from_percent(x: Self::Inner) -> Self { + let a = x.min(100.into()); + let b = Self::ACCURACY; + // if Self::ACCURACY % 100 > 0 then we need the correction for accuracy + let c = rational_mul_correction::(b, a, 100.into(), Rounding::Nearest); + Self::from_parts(a / 100.into() * b + c) + } /// Return the product of multiplication of this value by itself. - fn square(self) -> Self; + fn square(self) -> Self { + let p = Self::Upper::from(self.deconstruct()); + let q = Self::Upper::from(Self::ACCURACY); + Self::from_rational_approximation(p * p, q * q) + } + + /// Multiplication that always rounds down to a whole number. The standard `Mul` rounds to the + /// nearest whole number. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // round to nearest + /// assert_eq!(Percent::from_percent(34) * 10u64, 3); + /// assert_eq!(Percent::from_percent(36) * 10u64, 4); + /// + /// // round down + /// assert_eq!(Percent::from_percent(34).mul_floor(10u64), 3); + /// assert_eq!(Percent::from_percent(36).mul_floor(10u64), 3); + /// # } + /// ``` + fn mul_floor(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add + { + overflow_prune_mul::(b, self.deconstruct(), Rounding::Down) + } + + /// Multiplication that always rounds the result up to a whole number. The standard `Mul` + /// rounds to the nearest whole number. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // round to nearest + /// assert_eq!(Percent::from_percent(34) * 10u64, 3); + /// assert_eq!(Percent::from_percent(36) * 10u64, 4); + /// + /// // round up + /// assert_eq!(Percent::from_percent(34).mul_ceil(10u64), 4); + /// assert_eq!(Percent::from_percent(36).mul_ceil(10u64), 4); + /// # } + /// ``` + fn mul_ceil(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add + { + overflow_prune_mul::(b, self.deconstruct(), Rounding::Up) + } + + /// Saturating multiplication by the reciprocal of `self`. The result is rounded to the + /// nearest whole number and saturates at the numeric bounds instead of overflowing. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// assert_eq!(Percent::from_percent(50).saturating_reciprocal_mul(10u64), 20); + /// # } + /// ``` + fn saturating_reciprocal_mul(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add + Saturating + { + saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Nearest) + } + + /// Saturating multiplication by the reciprocal of `self`. The result is rounded down to the + /// nearest whole number and saturates at the numeric bounds instead of overflowing. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // round to nearest + /// assert_eq!(Percent::from_percent(60).saturating_reciprocal_mul(10u64), 17); + /// // round down + /// assert_eq!(Percent::from_percent(60).saturating_reciprocal_mul_floor(10u64), 16); + /// # } + /// ``` + fn saturating_reciprocal_mul_floor(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add + Saturating + { + saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Down) + } + + /// Saturating multiplication by the reciprocal of `self`. The result is rounded up to the + /// nearest whole number and saturates at the numeric bounds instead of overflowing. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // round to nearest + /// assert_eq!(Percent::from_percent(61).saturating_reciprocal_mul(10u64), 16); + /// // round up + /// assert_eq!(Percent::from_percent(61).saturating_reciprocal_mul_ceil(10u64), 17); + /// # } + /// ``` + fn saturating_reciprocal_mul_ceil(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add + Saturating + { + saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Up) + } + + /// Consume self and return the number of parts per thing. + fn deconstruct(self) -> Self::Inner; + + /// Build this type from a number of parts per thing. + fn from_parts(parts: Self::Inner) -> Self; /// Converts a fraction into `Self`. #[cfg(feature = "std")] @@ -63,88 +181,190 @@ pub trait PerThing: Sized + Saturating + Copy { /// 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`. + /// + /// Note that this always rounds _down_, i.e. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // 989/100 is technically closer to 99%. + /// assert_eq!( + /// Percent::from_rational_approximation(989, 1000), + /// Percent::from_parts(98), + /// ); + /// # } + /// ``` fn from_rational_approximation(p: N, q: N) -> Self - where N: Clone + Ord + From + TryInto + ops::Div; + where N: Clone + Ord + From + TryInto + TryInto + + ops::Div + ops::Rem + ops::Add; +} + +/// 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 { + Up, + Down, + Nearest, +} + +/// Saturating reciprocal multiplication. Compute `x / self`, saturating at the numeric +/// bounds instead of overflowing. +fn saturating_reciprocal_mul( + x: N, + part: P::Inner, + rounding: Rounding, +) -> N +where + N: Clone + From + UniqueSaturatedInto + ops::Div + ops::Mul + ops::Add + ops::Rem + Saturating, + P: PerThing, +{ + let maximum: N = P::ACCURACY.into(); + let c = rational_mul_correction::( + x.clone(), + P::ACCURACY, + part, + rounding, + ); + (x / part.into()).saturating_mul(maximum).saturating_add(c) +} + +/// 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 + From + UniqueSaturatedInto + ops::Div + ops::Mul + ops::Add + ops::Rem, + P: PerThing, +{ + let maximum: N = P::ACCURACY.into(); + let part_n: N = part.into(); + let c = rational_mul_correction::( + x.clone(), + part, + P::ACCURACY, + rounding, + ); + (x / maximum) * part_n + c +} + +/// Compute the error due to integer division in the expression `x / denom * numer`. +/// +/// Take the remainder of `x / denom` and multiply by `numer / denom`. The result can be added +/// 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: From + UniqueSaturatedInto + ops::Div + ops::Mul + ops::Add + ops::Rem, + P: PerThing, +{ + let numer_upper = P::Upper::from(numer); + let denom_n = N::from(denom); + let denom_upper = P::Upper::from(denom); + let rem = x.rem(denom_n); + // `rem` is less than `denom`, which fits in `P::Inner`. + let rem_inner = rem.saturated_into::(); + // `P::Upper` always fits `P::Inner::max_value().pow(2)`, thus it fits `rem * numer`. + let rem_mul_upper = P::Upper::from(rem_inner) * numer_upper; + // `rem` is less than `denom`, so `rem * numer / denom` is less than `numer`, which fits in + // `P::Inner`. + let mut rem_mul_div_inner = (rem_mul_upper / denom_upper).saturated_into::(); + match rounding { + // Already rounded down + Rounding::Down => {}, + // Round up if the fractional part of the result is non-zero. + Rounding::Up => if rem_mul_upper % denom_upper > 0.into() { + // `rem * numer / denom` is less than `numer`, so this will not overflow. + rem_mul_div_inner = 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 => 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 = rem_mul_div_inner + 1.into(); + }, + } + rem_mul_div_inner.into() } macro_rules! implement_per_thing { - ($name:ident, $test_mod:ident, [$($test_units:tt),+], $max:tt, $type:ty, $upper_type:ty, $title:expr $(,)?) => { - /// A fixed point representation of a number between in the range [0, 1]. + ( + $name:ident, + $test_mod:ident, + [$($test_units:tt),+], + $max:tt, + $type:ty, + $upper_type:ty, + $title:expr $(,)? + ) => { + /// A fixed point representation of a number in the range [0, 1]. /// #[doc = $title] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] - #[derive(Encode, Decode, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, CompactAs)] + #[derive(Encode, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, + RuntimeDebug, CompactAs)] pub struct $name($type); impl PerThing for $name { type Inner = $type; + type Upper = $upper_type; - /// The accuracy of this type. const ACCURACY: Self::Inner = $max; - /// Nothing. - fn zero() -> Self { Self(0) } - - /// `true` if this is nothing. - fn is_zero(&self) -> bool { self.0 == 0 } - - /// Everything. - fn one() -> Self { Self($max) } - - /// Consume self and deconstruct into a raw numeric type. + /// Consume self and return the number of parts per thing. fn deconstruct(self) -> Self::Inner { self.0 } - /// From an explicitly defined number of parts per maximum of the type. - fn from_parts(parts: Self::Inner) -> Self { - Self([parts, $max][(parts > $max) as usize]) - } - - /// Converts a percent into `Self`. Equal to `x / 100`. - fn from_percent(x: Self::Inner) -> Self { - Self([x, 100][(x > 100) as usize] * ($max / 100)) - } + /// Build this type from a number of parts per thing. + fn from_parts(parts: Self::Inner) -> Self { Self(parts.min($max)) } - /// Return the product of multiplication of this value by itself. - fn square(self) -> Self { - // both can be safely casted and multiplied. - let p: $upper_type = self.0 as $upper_type * self.0 as $upper_type; - let q: $upper_type = <$upper_type>::from($max) * <$upper_type>::from($max); - Self::from_rational_approximation(p, q) - } - - /// Converts a fraction into `Self`. #[cfg(feature = "std")] - fn from_fraction(x: f64) -> Self { Self((x * ($max as f64)) as Self::Inner) } + fn from_fraction(x: f64) -> Self { + Self::from_parts((x * $max as f64) as Self::Inner) + } - /// Approximate the fraction `p/q` into a per-thing fraction. This will never overflow. - /// - /// 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`. fn from_rational_approximation(p: N, q: N) -> Self - where N: Clone + Ord + From + TryInto + ops::Div + where N: Clone + Ord + From + TryInto + TryInto + + ops::Div + ops::Rem + ops::Add { + let div_ceil = |x: N, f: N| -> N { + let mut o = x.clone() / f.clone(); + let r = x.rem(f.clone()); + if r > N::from(0) { + o = o + N::from(1); + } + o + }; + // q cannot be zero. - let q = q.max((1 as Self::Inner).into()); + let q: N = q.max((1 as Self::Inner).into()); // p should not be bigger than q. - let p = p.min(q.clone()); + let p: N = p.min(q.clone()); - let factor = (q.clone() / $max.into()).max((1 as Self::Inner).into()); + let factor: N = div_ceil(q.clone(), $max.into()).max((1 as Self::Inner).into()); - // q cannot overflow: (q / (q/$max)) < 2 * $max. p < q hence p also cannot overflow. - // this implies that Self::Inner must be able to fit 2 * $max. - let q_reduce: Self::Inner = (q / factor.clone()) + // q cannot overflow: (q / (q/$max)) < $max. p < q hence p also cannot overflow. + let q_reduce: $type = (q.clone() / factor.clone()) .try_into() .map_err(|_| "Failed to convert") .expect( - "q / (q/$max) < (2 * $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: Self::Inner = (p / factor.clone()) + let p_reduce: $type = (p / factor) .try_into() .map_err(|_| "Failed to convert") .expect( - "q / (q/$max) < (2 * $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" ); @@ -159,11 +379,14 @@ macro_rules! implement_per_thing { } } - /// Implement const functions impl $name { /// From an explicitly defined number of parts per maximum of the type. /// /// This can be called at compile time. + // needed only for peru16. Since peru16 is the only type in which $max == + // $type::max_value(), rustc is being a smart-a** here by warning that the comparison + // is not needed. + #[allow(unused_comparisons)] pub const fn from_parts(parts: $type) -> Self { Self([parts, $max][(parts > $max) as usize]) } @@ -172,26 +395,110 @@ macro_rules! implement_per_thing { /// /// This can be created at compile time. pub const fn from_percent(x: $type) -> Self { - Self([x, 100][(x > 100) as usize] * ($max / 100)) + Self(([x, 100][(x > 100) as usize] as $upper_type * $max as $upper_type / 100) as $type) } - /// Everything. - /// - /// To avoid having to import `PerThing` when one needs to be used in test mocks. - #[cfg(feature = "std")] + /// See [`PerThing::one`]. pub fn one() -> Self { ::one() } + + /// See [`PerThing::is_one`]. + pub fn is_one(&self) -> bool { + PerThing::is_one(self) + } + + /// See [`PerThing::zero`]. + pub fn zero() -> Self { + ::zero() + } + + /// See [`PerThing::is_zero`]. + pub fn is_zero(&self) -> bool { + PerThing::is_zero(self) + } + + /// See [`PerThing::deconstruct`]. + pub fn deconstruct(self) -> $type { + PerThing::deconstruct(self) + } + + /// See [`PerThing::square`]. + pub fn square(self) -> Self { + PerThing::square(self) + } + + /// See [`PerThing::from_fraction`]. + #[cfg(feature = "std")] + pub fn from_fraction(x: f64) -> Self { + ::from_fraction(x) + } + + /// See [`PerThing::from_rational_approximation`]. + pub fn from_rational_approximation(p: N, q: N) -> Self + where N: Clone + Ord + From<$type> + TryInto<$type> + + TryInto<$upper_type> + ops::Div + ops::Rem + + ops::Add { + ::from_rational_approximation(p, q) + } + + /// See [`PerThing::mul_floor`]. + pub fn mul_floor(self, b: N) -> N + where N: Clone + From<$type> + UniqueSaturatedInto<$type> + + ops::Rem + ops::Div + ops::Mul + + ops::Add { + PerThing::mul_floor(self, b) + } + + /// See [`PerThing::mul_ceil`]. + pub fn mul_ceil(self, b: N) -> N + where N: Clone + From<$type> + UniqueSaturatedInto<$type> + + ops::Rem + ops::Div + ops::Mul + + ops::Add { + PerThing::mul_ceil(self, b) + } + + /// See [`PerThing::saturating_reciprocal_mul`]. + pub fn saturating_reciprocal_mul(self, b: N) -> N + where N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem + + ops::Div + ops::Mul + ops::Add + + Saturating { + PerThing::saturating_reciprocal_mul(self, b) + } + + /// See [`PerThing::saturating_reciprocal_mul_floor`]. + pub fn saturating_reciprocal_mul_floor(self, b: N) -> N + where N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem + + ops::Div + ops::Mul + ops::Add + + Saturating { + PerThing::saturating_reciprocal_mul_floor(self, b) + } + + /// See [`PerThing::saturating_reciprocal_mul_ceil`]. + pub fn saturating_reciprocal_mul_ceil(self, b: N) -> N + where N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem + + ops::Div + ops::Mul + ops::Add + + Saturating { + PerThing::saturating_reciprocal_mul_ceil(self, b) + } } impl Saturating for $name { + /// Saturating addition. Compute `self + rhs`, saturating at the numeric bounds instead of + /// overflowing. This operation is lossless if it does not saturate. fn saturating_add(self, rhs: Self) -> Self { // defensive-only: since `$max * 2 < $type::max_value()`, this can never overflow. Self::from_parts(self.0.saturating_add(rhs.0)) } + + /// Saturating subtraction. Compute `self - rhs`, saturating at the numeric bounds instead of + /// overflowing. This operation is lossless if it does not saturate. fn saturating_sub(self, rhs: Self) -> Self { Self::from_parts(self.0.saturating_sub(rhs.0)) } + + /// Saturating multiply. Compute `self * rhs`, saturating at the numeric bounds instead of + /// overflowing. This operation is lossy. fn saturating_mul(self, rhs: Self) -> Self { let a = self.0 as $upper_type; let b = rhs.0 as $upper_type; @@ -200,6 +507,53 @@ macro_rules! implement_per_thing { // This will always fit into $type. Self::from_parts(parts as $type) } + + /// Saturating exponentiation. Computes `self.pow(exp)`, saturating at the numeric + /// bounds instead of overflowing. This operation is lossy. + fn saturating_pow(self, exp: usize) -> Self { + if self.is_zero() || self.is_one() { + self + } else { + let p = <$name as PerThing>::Upper::from(self.deconstruct()); + let q = <$name as PerThing>::Upper::from(Self::ACCURACY); + let mut s = Self::one(); + for _ in 0..exp { + if s.is_zero() { + break; + } else { + // x^2 always fits in Self::Upper if x fits in Self::Inner. + // Verified by a test. + s = Self::from_rational_approximation( + <$name as PerThing>::Upper::from(s.deconstruct()) * p, + q * q, + ); + } + } + s + } + } + } + + impl codec::Decode for $name { + fn decode(input: &mut I) -> Result { + let inner = <$type as codec::Decode>::decode(input)?; + + if inner <= ::ACCURACY { + Ok(Self(inner)) + } else { + Err("Value is greater than allowed maximum!".into()) + } + } + } + + impl crate::traits::Bounded for $name { + fn min_value() -> Self { + ::zero() + } + + fn max_value() -> Self { + ::one() + } } impl ops::Div for $name { @@ -212,9 +566,9 @@ macro_rules! implement_per_thing { } } - /// Overflow-prune multiplication. + /// Non-overflow multiplication. /// - /// tailored to be used with a balance type. + /// This is tailored to be used with a balance type. impl ops::Mul for $name where N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem @@ -222,36 +576,7 @@ macro_rules! implement_per_thing { { type Output = N; fn mul(self, b: N) -> Self::Output { - let maximum: N = $max.into(); - let upper_max: $upper_type = $max.into(); - let part: N = self.0.into(); - - let rem_multiplied_divided = { - let rem = b.clone().rem(maximum.clone()); - - // `rem_sized` is inferior to $max, thus it fits into $type. This is assured by - // a test. - let rem_sized = rem.saturated_into::<$type>(); - - // `self` and `rem_sized` are inferior to $max, thus the product is less than - // $max^2 and fits into $upper_type. This is assured by a test. - let rem_multiplied_upper = rem_sized as $upper_type * self.0 as $upper_type; - - // `rem_multiplied_upper` is less than $max^2 therefore divided by $max it fits - // in $type. remember that $type always fits $max. - let mut rem_multiplied_divided_sized = - (rem_multiplied_upper / upper_max) as $type; - // fix a tiny rounding error - if rem_multiplied_upper % upper_max > upper_max / 2 { - rem_multiplied_divided_sized += 1; - } - - // `rem_multiplied_divided_sized` is inferior to b, thus it can be converted - // back to N type - rem_multiplied_divided_sized.into() - }; - - (b / maximum) * part + rem_multiplied_divided + overflow_prune_mul::(b, self.deconstruct(), Rounding::Nearest) } } @@ -261,20 +586,25 @@ macro_rules! implement_per_thing { use super::{$name, Saturating, RuntimeDebug, PerThing}; use crate::traits::Zero; - #[test] fn macro_expanded_correctly() { - // needed for the `from_percent` to work. - assert!($max >= 100); - assert!($max % 100 == 0); + // needed for the `from_percent` to work. UPDATE: this is no longer needed; yet note + // that tests that use percentage or fractions such as $name::from_fraction(0.2) to + // create values will most likely be inaccurate when used with per_things that are + // not multiples of 100. + // assert!($max >= 100); + // assert!($max % 100 == 0); // needed for `from_rational_approximation` - assert!(2 * $max < <$type>::max_value()); + assert!(2 * ($max as $upper_type) < <$upper_type>::max_value()); assert!(<$upper_type>::from($max) < <$upper_type>::max_value()); // for something like percent they can be the same. assert!((<$type>::max_value() as $upper_type) <= <$upper_type>::max_value()); assert!(<$upper_type>::from($max).checked_mul($max.into()).is_some()); + + // make sure saturating_pow won't overflow the upper type + assert!(<$upper_type>::from($max) * <$upper_type>::from($max) < <$upper_type>::max_value()); } #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug)] @@ -298,7 +628,7 @@ macro_rules! implement_per_thing { (63, 1), (64, 2), (65, 2), - (<$type>::max_value(), <$type>::max_value().encode().len() + 1) + // (<$type>::max_value(), <$type>::max_value().encode().len() + 1) ]; for &(n, l) in &tests { let compact: codec::Compact<$name> = $name(n).into(); @@ -311,39 +641,100 @@ macro_rules! implement_per_thing { } } + #[test] + fn fail_on_invalid_encoded_value() { + let value = <$upper_type>::from($max) * 2; + let casted = value as $type; + let encoded = casted.encode(); + + // For types where `$max == $type::maximum()` we can not + if <$upper_type>::from(casted) == value { + assert_eq!( + $name::decode(&mut &encoded[..]), + Err("Value is greater than allowed maximum!".into()), + ); + } + } + #[test] fn per_thing_api_works() { // some really basic stuff assert_eq!($name::zero(), $name::from_parts(Zero::zero())); assert_eq!($name::one(), $name::from_parts($max)); assert_eq!($name::ACCURACY, $max); + assert_eq!($name::from_percent(0), $name::from_parts(Zero::zero())); assert_eq!($name::from_percent(10), $name::from_parts($max / 10)); assert_eq!($name::from_percent(100), $name::from_parts($max)); + assert_eq!($name::from_percent(200), $name::from_parts($max)); + + assert_eq!($name::from_fraction(0.0), $name::from_parts(Zero::zero())); + assert_eq!($name::from_fraction(0.1), $name::from_parts($max / 10)); + assert_eq!($name::from_fraction(1.0), $name::from_parts($max)); + } + + macro_rules! u256ify { + ($val:expr) => { + Into::::into($val) + }; } macro_rules! per_thing_mul_test { ($num_type:tt) => { // multiplication from all sort of from_percent assert_eq!( - $name::from_percent(100) * $num_type::max_value(), + $name::from_fraction(1.0) * $num_type::max_value(), $num_type::max_value() ); - assert_eq_error_rate!( - $name::from_percent(99) * $num_type::max_value(), - ((Into::::into($num_type::max_value()) * 99u32) / 100u32).as_u128() as $num_type, - 1, - ); - assert_eq!( - $name::from_percent(50) * $num_type::max_value(), - $num_type::max_value() / 2, - ); - assert_eq_error_rate!( - $name::from_percent(1) * $num_type::max_value(), - $num_type::max_value() / 100, - 1, - ); - assert_eq!($name::from_percent(0) * $num_type::max_value(), 0); + if $max % 100 == 0 { + assert_eq_error_rate!( + $name::from_percent(99) * $num_type::max_value(), + ((Into::::into($num_type::max_value()) * 99u32) / 100u32).as_u128() as $num_type, + 1, + ); + assert_eq!( + $name::from_fraction(0.5) * $num_type::max_value(), + $num_type::max_value() / 2, + ); + assert_eq_error_rate!( + $name::from_percent(1) * $num_type::max_value(), + $num_type::max_value() / 100, + 1, + ); + } else { + assert_eq!( + $name::from_fraction(0.99) * <$num_type>::max_value(), + ( + ( + u256ify!($name::from_fraction(0.99).0) * + u256ify!(<$num_type>::max_value()) / + u256ify!($max) + ).as_u128() + ) as $num_type, + ); + assert_eq!( + $name::from_fraction(0.50) * <$num_type>::max_value(), + ( + ( + u256ify!($name::from_fraction(0.50).0) * + u256ify!(<$num_type>::max_value()) / + u256ify!($max) + ).as_u128() + ) as $num_type, + ); + assert_eq!( + $name::from_fraction(0.01) * <$num_type>::max_value(), + ( + ( + u256ify!($name::from_fraction(0.01).0) * + u256ify!(<$num_type>::max_value()) / + u256ify!($max) + ).as_u128() + ) as $num_type, + ); + } + + assert_eq!($name::from_fraction(0.0) * $num_type::max_value(), 0); // // multiplication with bounds assert_eq!($name::one() * $num_type::max_value(), $num_type::max_value()); @@ -356,17 +747,20 @@ macro_rules! implement_per_thing { use primitive_types::U256; // accuracy test - assert_eq!($name::from_rational_approximation(1 as $type, 3) * 30 as $type, 10); + assert_eq!( + $name::from_rational_approximation(1 as $type, 3) * 30 as $type, + 10, + ); $(per_thing_mul_test!($test_units);)* } #[test] fn per_thing_mul_rounds_to_nearest_number() { - 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); + assert_eq!($name::from_fraction(0.33) * 10u64, 3); + assert_eq!($name::from_fraction(0.34) * 10u64, 3); + assert_eq!($name::from_fraction(0.35) * 10u64, 3); + assert_eq!($name::from_fraction(0.36) * 10u64, 4); } #[test] @@ -398,31 +792,32 @@ macro_rules! implement_per_thing { ); assert_eq!( $name::from_rational_approximation(1 as $num_type, 10), - $name::from_percent(10), + $name::from_fraction(0.10), ); assert_eq!( $name::from_rational_approximation(1 as $num_type, 4), - $name::from_percent(25), + $name::from_fraction(0.25), ); assert_eq!( $name::from_rational_approximation(1 as $num_type, 4), $name::from_rational_approximation(2 as $num_type, 8), ); // no accurate anymore but won't overflow. - assert_eq!( + assert_eq_error_rate!( $name::from_rational_approximation( $num_type::max_value() - 1, $num_type::max_value() - ), - $name::one(), + ).0 as $upper_type, + $name::one().0 as $upper_type, + 2, ); assert_eq_error_rate!( $name::from_rational_approximation( $num_type::max_value() / 3, $num_type::max_value() - ).0, - $name::from_parts($max / 3).0, - 2 + ).0 as $upper_type, + $name::from_parts($max / 3).0 as $upper_type, + 2, ); assert_eq!( $name::from_rational_approximation(1, $num_type::max_value()), @@ -436,13 +831,14 @@ macro_rules! implement_per_thing { // This is just to make sure something like Percent which _might_ get built from a // u8 does not overflow in the context of this test. let max_value = <$upper_type>::from($max); + // almost at the edge assert_eq!( - $name::from_rational_approximation($max - 1, $max + 1), + $name::from_rational_approximation(max_value - 1, max_value + 1), $name::from_parts($max - 2), ); assert_eq!( - $name::from_rational_approximation(1, $max-1), + $name::from_rational_approximation(1, $max - 1), $name::from_parts(1), ); assert_eq!( @@ -450,76 +846,83 @@ macro_rules! implement_per_thing { $name::from_parts(1), ); assert_eq!( - $name::from_rational_approximation(2, 2 * $max - 1), + $name::from_rational_approximation(2, 2 * max_value - 1), $name::from_parts(1), ); assert_eq!( - $name::from_rational_approximation(1, $max+1), + $name::from_rational_approximation(1, max_value + 1), $name::zero(), ); assert_eq!( $name::from_rational_approximation(3 * max_value / 2, 3 * max_value), - $name::from_percent(50), + $name::from_fraction(0.5), ); + $(per_thing_from_rationale_approx_test!($test_units);)* } #[test] fn per_things_mul_operates_in_output_type() { - // assert_eq!($name::from_percent(50) * 100u32, 50u32); - assert_eq!($name::from_percent(50) * 100u64, 50u64); - assert_eq!($name::from_percent(50) * 100u128, 50u128); + // assert_eq!($name::from_fraction(0.5) * 100u32, 50u32); + assert_eq!($name::from_fraction(0.5) * 100u64, 50u64); + assert_eq!($name::from_fraction(0.5) * 100u128, 50u128); } #[test] fn per_thing_saturating_op_works() { - assert_eq!( - $name::from_percent(50).saturating_add($name::from_percent(40)), - $name::from_percent(90) + assert_eq_error_rate!( + $name::from_fraction(0.5).saturating_add($name::from_fraction(0.4)).0 as $upper_type, + $name::from_fraction(0.9).0 as $upper_type, + 2, ); - assert_eq!( - $name::from_percent(50).saturating_add($name::from_percent(50)), - $name::from_percent(100) + assert_eq_error_rate!( + $name::from_fraction(0.5).saturating_add($name::from_fraction(0.5)).0 as $upper_type, + $name::one().0 as $upper_type, + 2, ); assert_eq!( - $name::from_percent(60).saturating_add($name::from_percent(50)), - $name::from_percent(100) + $name::from_fraction(0.6).saturating_add($name::from_fraction(0.5)), + $name::one(), ); - assert_eq!( - $name::from_percent(60).saturating_sub($name::from_percent(50)), - $name::from_percent(10) + assert_eq_error_rate!( + $name::from_fraction(0.6).saturating_sub($name::from_fraction(0.5)).0 as $upper_type, + $name::from_fraction(0.1).0 as $upper_type, + 2, ); assert_eq!( - $name::from_percent(60).saturating_sub($name::from_percent(60)), - $name::from_percent(0) + $name::from_fraction(0.6).saturating_sub($name::from_fraction(0.6)), + $name::from_fraction(0.0), ); assert_eq!( - $name::from_percent(60).saturating_sub($name::from_percent(70)), - $name::from_percent(0) + $name::from_fraction(0.6).saturating_sub($name::from_fraction(0.7)), + $name::from_fraction(0.0), ); - assert_eq!( - $name::from_percent(50).saturating_mul($name::from_percent(50)), - $name::from_percent(25) + assert_eq_error_rate!( + $name::from_fraction(0.5).saturating_mul($name::from_fraction(0.5)).0 as $upper_type, + $name::from_fraction(0.25).0 as $upper_type, + 2, ); - assert_eq!( - $name::from_percent(20).saturating_mul($name::from_percent(20)), - $name::from_percent(4) + assert_eq_error_rate!( + $name::from_fraction(0.2).saturating_mul($name::from_fraction(0.2)).0 as $upper_type, + $name::from_fraction(0.04).0 as $upper_type, + 2, ); - assert_eq!( - $name::from_percent(10).saturating_mul($name::from_percent(10)), - $name::from_percent(1) + assert_eq_error_rate!( + $name::from_fraction(0.1).saturating_mul($name::from_fraction(0.1)).0 as $upper_type, + $name::from_fraction(0.01).0 as $upper_type, + 1, ); } #[test] fn per_thing_square_works() { - assert_eq!($name::from_percent(100).square(), $name::from_percent(100)); - assert_eq!($name::from_percent(50).square(), $name::from_percent(25)); - assert_eq!($name::from_percent(10).square(), $name::from_percent(1)); + assert_eq!($name::from_fraction(1.0).square(), $name::from_fraction(1.0)); + assert_eq!($name::from_fraction(0.5).square(), $name::from_fraction(0.25)); + assert_eq!($name::from_fraction(0.1).square(), $name::from_fraction(0.01)); assert_eq!( - $name::from_percent(2).square(), + $name::from_fraction(0.02).square(), $name::from_parts((4 * <$upper_type>::from($max) / 100 / 100) as $type) ); } @@ -527,22 +930,203 @@ macro_rules! implement_per_thing { #[test] fn per_things_div_works() { // normal - assert_eq!($name::from_percent(10) / $name::from_percent(20), - $name::from_percent(50) + assert_eq_error_rate!( + ($name::from_fraction(0.1) / $name::from_fraction(0.20)).0 as $upper_type, + $name::from_fraction(0.50).0 as $upper_type, + 2, ); - assert_eq!($name::from_percent(10) / $name::from_percent(10), - $name::from_percent(100) + assert_eq_error_rate!( + ($name::from_fraction(0.1) / $name::from_fraction(0.10)).0 as $upper_type, + $name::from_fraction(1.0).0 as $upper_type, + 2, ); - assert_eq!($name::from_percent(10) / $name::from_percent(0), - $name::from_percent(100) + assert_eq_error_rate!( + ($name::from_fraction(0.1) / $name::from_fraction(0.0)).0 as $upper_type, + $name::from_fraction(1.0).0 as $upper_type, + 2, ); // will not overflow - assert_eq!($name::from_percent(10) / $name::from_percent(5), - $name::from_percent(100) + assert_eq_error_rate!( + ($name::from_fraction(0.10) / $name::from_fraction(0.05)).0 as $upper_type, + $name::from_fraction(1.0).0 as $upper_type, + 2, + ); + assert_eq_error_rate!( + ($name::from_fraction(1.0) / $name::from_fraction(0.5)).0 as $upper_type, + $name::from_fraction(1.0).0 as $upper_type, + 2, + ); + } + + #[test] + fn saturating_pow_works() { + // x^0 == 1 + assert_eq!( + $name::from_parts($max / 2).saturating_pow(0), + $name::from_parts($max), + ); + + // x^1 == x + assert_eq!( + $name::from_parts($max / 2).saturating_pow(1), + $name::from_parts($max / 2), + ); + + // x^2 + assert_eq!( + $name::from_parts($max / 2).saturating_pow(2), + $name::from_parts($max / 2).square(), + ); + + // x^3 + assert_eq!( + $name::from_parts($max / 2).saturating_pow(3), + $name::from_parts($max / 8), + ); + + // 0^n == 0 + assert_eq!( + $name::from_parts(0).saturating_pow(3), + $name::from_parts(0), ); - assert_eq!($name::from_percent(100) / $name::from_percent(50), - $name::from_percent(100) + + // 1^n == 1 + assert_eq!( + $name::from_parts($max).saturating_pow(3), + $name::from_parts($max), + ); + + // (x < 1)^inf == 0 (where 2.pow(31) ~ inf) + assert_eq!( + $name::from_parts($max / 2).saturating_pow(2usize.pow(31)), + $name::from_parts(0), + ); + } + + #[test] + fn saturating_reciprocal_mul_works() { + // divide by 1 + assert_eq!( + $name::from_parts($max).saturating_reciprocal_mul(<$type>::from(10u8)), + 10, + ); + // divide by 1/2 + assert_eq!( + $name::from_parts($max / 2).saturating_reciprocal_mul(<$type>::from(10u8)), + 20, + ); + // saturate + assert_eq!( + $name::from_parts(1).saturating_reciprocal_mul($max), + <$type>::max_value(), + ); + // round to nearest + assert_eq!( + $name::from_percent(60).saturating_reciprocal_mul(<$type>::from(10u8)), + 17, + ); + // round down + assert_eq!( + $name::from_percent(60).saturating_reciprocal_mul_floor(<$type>::from(10u8)), + 16, + ); + // round to nearest + assert_eq!( + $name::from_percent(61).saturating_reciprocal_mul(<$type>::from(10u8)), + 16, + ); + // round up + assert_eq!( + $name::from_percent(61).saturating_reciprocal_mul_ceil(<$type>::from(10u8)), + 17, + ); + } + + #[test] + fn saturating_truncating_mul_works() { + assert_eq!( + $name::from_percent(49).mul_floor(10 as $type), + 4, + ); + let a: $upper_type = $name::from_percent(50).mul_floor(($max as $upper_type).pow(2)); + let b: $upper_type = ($max as $upper_type).pow(2) / 2; + if $max % 2 == 0 { + assert_eq!(a, b); + } else { + // difference should be less that 1%, IE less than the error in `from_percent` + assert!(b - a < ($max as $upper_type).pow(2) / 100 as $upper_type); + } + } + + #[test] + fn rational_mul_correction_works() { + assert_eq!( + super::rational_mul_correction::<$type, $name>( + <$type>::max_value(), + <$type>::max_value(), + <$type>::max_value(), + super::Rounding::Nearest, + ), + 0, + ); + assert_eq!( + super::rational_mul_correction::<$type, $name>( + <$type>::max_value() - 1, + <$type>::max_value(), + <$type>::max_value(), + super::Rounding::Nearest, + ), + <$type>::max_value() - 1, + ); + assert_eq!( + super::rational_mul_correction::<$upper_type, $name>( + ((<$type>::max_value() - 1) as $upper_type).pow(2), + <$type>::max_value(), + <$type>::max_value(), + super::Rounding::Nearest, + ), + 1, + ); + // ((max^2 - 1) % max) * max / max == max - 1 + assert_eq!( + super::rational_mul_correction::<$upper_type, $name>( + (<$type>::max_value() as $upper_type).pow(2) - 1, + <$type>::max_value(), + <$type>::max_value(), + super::Rounding::Nearest, + ), + (<$type>::max_value() - 1).into(), + ); + // (max % 2) * max / 2 == max / 2 + assert_eq!( + super::rational_mul_correction::<$upper_type, $name>( + (<$type>::max_value() as $upper_type).pow(2), + <$type>::max_value(), + 2 as $type, + super::Rounding::Nearest, + ), + <$type>::max_value() as $upper_type / 2, + ); + // ((max^2 - 1) % max) * 2 / max == 2 (rounded up) + assert_eq!( + super::rational_mul_correction::<$upper_type, $name>( + (<$type>::max_value() as $upper_type).pow(2) - 1, + 2 as $type, + <$type>::max_value(), + super::Rounding::Nearest, + ), + 2, + ); + // ((max^2 - 1) % max) * 2 / max == 1 (rounded down) + assert_eq!( + super::rational_mul_correction::<$upper_type, $name>( + (<$type>::max_value() as $upper_type).pow(2) - 1, + 2 as $type, + <$type>::max_value(), + super::Rounding::Down, + ), + 1, ); } } @@ -558,6 +1142,15 @@ implement_per_thing!( u16, "_Percent_", ); +implement_per_thing!( + PerU16, + test_peru16, + [u32, u64, u128], + 65535_u16, + u16, + u32, + "_Parts per 65535_", +); implement_per_thing!( Permill, test_permill, diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 75adf0e1363dd6c460707d06a4852f2f39c4fbe2..23f8f23f0bd77b783dfc1908b0efc6354f9fc4a8 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -21,7 +21,7 @@ use codec::HasCompact; pub use integer_sqrt::IntegerSquareRoot; pub use num_traits::{ Zero, One, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, - CheckedShl, CheckedShr + CheckedShl, CheckedShr, checked_pow }; use sp_std::ops::{ Add, Sub, Mul, Div, Rem, AddAssign, SubAssign, MulAssign, DivAssign, @@ -104,29 +104,41 @@ impl + Sized> UniqueSaturatedInto for S { } } -/// Simple trait to use checked mul and max value to give a saturated mul operation over -/// supported types. +/// Saturating arithmetic operations, returning maximum or minimum values instead of overflowing. pub trait Saturating { - /// Saturated addition - if the product can't fit in the type then just use max-value. - fn saturating_add(self, o: Self) -> Self; - - /// Saturated subtraction - if the product can't fit in the type then just use max-value. - fn saturating_sub(self, o: Self) -> Self; - - /// Saturated multiply - if the product can't fit in the type then just use max-value. - fn saturating_mul(self, o: Self) -> Self; + /// Saturating addition. Compute `self + rhs`, saturating at the numeric bounds instead of + /// overflowing. + fn saturating_add(self, rhs: Self) -> Self; + + /// Saturating subtraction. Compute `self - rhs`, saturating at the numeric bounds instead of + /// overflowing. + fn saturating_sub(self, rhs: Self) -> Self; + + /// Saturating multiply. Compute `self * rhs`, saturating at the numeric bounds instead of + /// overflowing. + fn saturating_mul(self, rhs: Self) -> Self; + + /// Saturating exponentiation. Compute `self.pow(exp)`, saturating at the numeric bounds + /// instead of overflowing. + fn saturating_pow(self, exp: usize) -> Self; } -impl Saturating for T { +impl Saturating for T { fn saturating_add(self, o: Self) -> Self { ::saturating_add(self, o) } + fn saturating_sub(self, o: Self) -> Self { ::saturating_sub(self, o) } + fn saturating_mul(self, o: Self) -> Self { self.checked_mul(&o).unwrap_or_else(Bounded::max_value) } + + fn saturating_pow(self, exp: usize) -> Self { + checked_pow(self, exp).unwrap_or_else(Bounded::max_value) + } } /// Convenience type to work around the highly unergonomic syntax needed diff --git a/primitives/authority-discovery/Cargo.toml b/primitives/authority-discovery/Cargo.toml index b4197c56d7e5ff912070b08af284a9693e74a73e..f37b67fab1ce8d476fef66656b3c5013701edbf9 100644 --- a/primitives/authority-discovery/Cargo.toml +++ b/primitives/authority-discovery/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-authority-discovery" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] description = "Authority discovery primitives" edition = "2018" @@ -9,11 +9,11 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../application-crypto" } -codec = { package = "parity-scale-codec", default-features = false, version = "1.2.0" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../api" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } +sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../application-crypto" } +codec = { package = "parity-scale-codec", default-features = false, version = "1.3.0" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../api" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } [features] default = ["std"] @@ -24,3 +24,6 @@ std = [ "sp-api/std", "sp-runtime/std" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/authority-discovery/src/lib.rs b/primitives/authority-discovery/src/lib.rs index 8926825525976f9d284bc1b6969ddfde936aff99..68680ad75946545f93d8d0c5ef5c3004a3623efc 100644 --- a/primitives/authority-discovery/src/lib.rs +++ b/primitives/authority-discovery/src/lib.rs @@ -21,8 +21,25 @@ use sp_std::vec::Vec; mod app { - use sp_application_crypto::{app_crypto, key_types::AUTHORITY_DISCOVERY, sr25519}; + use sp_application_crypto::{ + CryptoTypePublicPair, + key_types::AUTHORITY_DISCOVERY, + Public as _, + app_crypto, + sr25519}; app_crypto!(sr25519, AUTHORITY_DISCOVERY); + + impl From for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } + } + + impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(sr25519::CRYPTO_ID, key.to_raw_vec()) + } + } } sp_application_crypto::with_pair! { diff --git a/primitives/authorship/Cargo.toml b/primitives/authorship/Cargo.toml index c5215631cca28c413460b2e77107cd1c575e7122..a4d5aa03c2382c3c93f278bca825c7ca08688951 100644 --- a/primitives/authorship/Cargo.toml +++ b/primitives/authorship/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-authorship" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] description = "Authorship primitives" edition = "2018" @@ -9,10 +9,10 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../inherents" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../inherents" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } [features] default = [ "std" ] @@ -22,3 +22,6 @@ std = [ "sp-inherents/std", "sp-runtime/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/block-builder/Cargo.toml b/primitives/block-builder/Cargo.toml index b5cb592d55b4109264d57b99c349d901626b8706..df33b2c955ff096b1bd861769d1dd5c56497a867 100644 --- a/primitives/block-builder/Cargo.toml +++ b/primitives/block-builder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-block-builder" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,11 +9,11 @@ repository = "https://github.com/paritytech/substrate/" description = "The block builder runtime api." [dependencies] -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../api" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../inherents" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../api" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../inherents" } [features] default = [ "std" ] @@ -24,3 +24,6 @@ std = [ "sp-api/std", "sp-std/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/blockchain/Cargo.toml b/primitives/blockchain/Cargo.toml index 5fb8ce3574719cd3af0f0231b682c38110e763cc..49e6baead1fb82a919b3d7ca40fe0a6045ee0524 100644 --- a/primitives/blockchain/Cargo.toml +++ b/primitives/blockchain/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-blockchain" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -15,8 +15,11 @@ log = "0.4.8" lru = "0.4.0" parking_lot = "0.10.0" derive_more = "0.99.2" -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-consensus = { version = "0.8.0-alpha.2", path = "../consensus/common" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../runtime" } -sp-block-builder = { version = "2.0.0-alpha.2", path = "../block-builder" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../state-machine" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-consensus = { version = "0.8.0-alpha.5", path = "../consensus/common" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../runtime" } +sp-block-builder = { version = "2.0.0-alpha.5", path = "../block-builder" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../state-machine" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/blockchain/src/error.rs b/primitives/blockchain/src/error.rs index a4ec9c29952ab986f2615bec87dee37c7de81fae..e479b8abe918ee881dbb4fa79cdf824cfe6fb14e 100644 --- a/primitives/blockchain/src/error.rs +++ b/primitives/blockchain/src/error.rs @@ -127,6 +127,8 @@ pub enum Error { /// Incomplete block import pipeline. #[display(fmt = "Incomplete block import pipeline.")] IncompletePipeline, + #[display(fmt = "Transaction pool not ready for block production.")] + TransactionPoolNotReady, /// A convenience variant for String #[display(fmt = "{}", _0)] Msg(String), diff --git a/primitives/consensus/aura/Cargo.toml b/primitives/consensus/aura/Cargo.toml index 04d411939975f096b6b3d3536d7a6d992ddbe55b..99ce51a229c72e045e97b92f9e8c8f8d3aff2513 100644 --- a/primitives/consensus/aura/Cargo.toml +++ b/primitives/consensus/aura/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-consensus-aura" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Primitives for Aura consensus" edition = "2018" @@ -9,13 +9,13 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../../application-crypto" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../std" } -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../api" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../runtime" } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../inherents" } -sp-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../../timestamp" } +sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../../application-crypto" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../std" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../api" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../runtime" } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../inherents" } +sp-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../timestamp" } [features] default = ["std"] @@ -28,3 +28,6 @@ std = [ "sp-inherents/std", "sp-timestamp/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/consensus/babe/Cargo.toml b/primitives/consensus/babe/Cargo.toml index f910b73ed6e0045ce475b884eafc86a6c0389f86..195a54a5910c419957f053a6f928aa369b467110 100644 --- a/primitives/consensus/babe/Cargo.toml +++ b/primitives/consensus/babe/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-consensus-babe" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Primitives for BABE consensus" edition = "2018" @@ -9,15 +9,15 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../../application-crypto" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../std" } -schnorrkel = { version = "0.8.5", features = ["preaudit_deprecated"], optional = true } -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../api" } -sp-consensus = { version = "0.8.0-alpha.2", optional = true, path = "../common" } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../inherents" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../runtime" } -sp-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../../timestamp" } +sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../../application-crypto" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../std" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../api" } +sp-consensus = { version = "0.8.0-alpha.5", optional = true, path = "../common" } +sp-consensus-vrf = { version = "0.8.0-alpha.5", path = "../vrf", default-features = false } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../inherents" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../runtime" } +sp-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../timestamp" } [features] default = ["std"] @@ -25,10 +25,13 @@ std = [ "sp-application-crypto/std", "codec/std", "sp-std/std", - "schnorrkel", "sp-api/std", "sp-consensus", + "sp-consensus-vrf/std", "sp-inherents/std", "sp-runtime/std", "sp-timestamp/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/consensus/babe/src/digests.rs b/primitives/consensus/babe/src/digests.rs index 7ec0f9b977cc7d921d64e0605db52d5c2c5bc3f3..6079aa88c87492538fbf3caefdd3775d4cdd9292 100644 --- a/primitives/consensus/babe/src/digests.rs +++ b/primitives/consensus/babe/src/digests.rs @@ -18,65 +18,96 @@ #[cfg(feature = "std")] use super::{BABE_ENGINE_ID, AuthoritySignature}; -#[cfg(not(feature = "std"))] -use super::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}; use super::{AuthorityId, AuthorityIndex, SlotNumber, BabeAuthorityWeight}; #[cfg(feature = "std")] use sp_runtime::{DigestItem, generic::OpaqueDigestItemId}; #[cfg(feature = "std")] -use std::fmt::Debug; +use std::{fmt::Debug, convert::{TryFrom, TryInto}}; use codec::{Decode, Encode}; #[cfg(feature = "std")] -use codec::{Codec, Input, Error}; -#[cfg(feature = "std")] -use schnorrkel::{ - SignatureError, errors::MultiSignatureStage, - vrf::{VRFProof, VRFOutput, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH} -}; +use codec::Codec; use sp_std::vec::Vec; +use sp_runtime::RuntimeDebug; +use sp_consensus_vrf::schnorrkel::{self, Randomness}; +#[cfg(feature = "std")] +use sp_consensus_vrf::schnorrkel::SignatureError; + +/// Raw BABE primary slot assignment pre-digest. +#[derive(Clone, RuntimeDebug, Encode, Decode)] +pub struct RawPrimaryPreDigest { + /// Authority index + pub authority_index: super::AuthorityIndex, + /// Slot number + pub slot_number: SlotNumber, + /// VRF output + pub vrf_output: VRFOutput, + /// VRF proof + pub vrf_proof: VRFProof, +} + +#[cfg(feature = "std")] +/// BABE primary slot assignment pre-digest for std environment. +pub type PrimaryPreDigest = RawPrimaryPreDigest; +#[cfg(feature = "std")] +impl TryFrom for PrimaryPreDigest { + type Error = SignatureError; + + fn try_from(raw: RawPrimaryPreDigest) -> Result { + Ok(PrimaryPreDigest { + authority_index: raw.authority_index, + slot_number: raw.slot_number, + vrf_output: raw.vrf_output.try_into()?, + vrf_proof: raw.vrf_proof.try_into()?, + }) + } +} + +/// BABE secondary slot assignment pre-digest. +#[derive(Clone, RuntimeDebug, Encode, Decode)] +pub struct SecondaryPreDigest { + /// Authority index + /// + /// This is not strictly-speaking necessary, since the secondary slots + /// are assigned based on slot number and epoch randomness. But including + /// it makes things easier for higher-level users of the chain data to + /// be aware of the author of a secondary-slot block. + pub authority_index: super::AuthorityIndex, + /// Slot number + pub slot_number: SlotNumber, +} /// 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). -#[cfg(feature = "std")] -#[derive(Clone, Debug)] -pub enum PreDigest { +#[derive(Clone, RuntimeDebug, Encode, Decode)] +pub enum RawPreDigest { /// A primary VRF-based slot assignment. - Primary { - /// VRF output - vrf_output: VRFOutput, - /// VRF proof - vrf_proof: VRFProof, - /// Authority index - authority_index: super::AuthorityIndex, - /// Slot number - slot_number: SlotNumber, - }, + #[codec(index = "1")] + Primary(RawPrimaryPreDigest), /// A secondary deterministic slot assignment. - Secondary { - /// Authority index - authority_index: super::AuthorityIndex, - /// Slot number - slot_number: SlotNumber, - }, + #[codec(index = "2")] + Secondary(SecondaryPreDigest), } #[cfg(feature = "std")] -impl PreDigest { +/// A BABE pre-runtime digest for std. +pub type PreDigest = RawPreDigest; + +impl RawPreDigest { /// Returns the slot number of the pre digest. pub fn authority_index(&self) -> AuthorityIndex { match self { - PreDigest::Primary { authority_index, .. } => *authority_index, - PreDigest::Secondary { authority_index, .. } => *authority_index, + RawPreDigest::Primary(primary) => primary.authority_index, + RawPreDigest::Secondary(secondary) => secondary.authority_index, } } /// Returns the slot number of the pre digest. pub fn slot_number(&self) -> SlotNumber { match self { - PreDigest::Primary { slot_number, .. } => *slot_number, - PreDigest::Secondary { slot_number, .. } => *slot_number, + RawPreDigest::Primary(primary) => primary.slot_number, + RawPreDigest::Secondary(secondary) => secondary.slot_number, } } @@ -84,121 +115,33 @@ impl PreDigest { /// of the chain. pub fn added_weight(&self) -> crate::BabeBlockWeight { match self { - PreDigest::Primary { .. } => 1, - PreDigest::Secondary { .. } => 0, - } - } -} - -/// A raw version of `BabePreDigest`, usable on `no_std`. -#[derive(Copy, Clone, Encode, Decode)] -pub enum RawPreDigest { - /// A primary VRF-based slot assignment. - #[codec(index = "1")] - Primary { - /// Authority index - authority_index: AuthorityIndex, - /// Slot number - slot_number: SlotNumber, - /// VRF output - vrf_output: [u8; VRF_OUTPUT_LENGTH], - /// VRF proof - vrf_proof: [u8; VRF_PROOF_LENGTH], - }, - /// A secondary deterministic slot assignment. - #[codec(index = "2")] - Secondary { - /// Authority index - /// - /// This is not strictly-speaking necessary, since the secondary slots - /// are assigned based on slot number and epoch randomness. But including - /// it makes things easier for higher-level users of the chain data to - /// be aware of the author of a secondary-slot block. - authority_index: AuthorityIndex, - /// Slot number - slot_number: SlotNumber, - }, -} - -impl RawPreDigest { - /// Returns the slot number of the pre digest. - pub fn slot_number(&self) -> SlotNumber { - match self { - RawPreDigest::Primary { slot_number, .. } => *slot_number, - RawPreDigest::Secondary { slot_number, .. } => *slot_number, + RawPreDigest::Primary(_) => 1, + RawPreDigest::Secondary(_) => 0, } } } #[cfg(feature = "std")] -impl Encode for PreDigest { - fn encode(&self) -> Vec { - let raw = match self { - PreDigest::Primary { - vrf_output, - vrf_proof, - authority_index, - slot_number, - } => { - RawPreDigest::Primary { - vrf_output: *vrf_output.as_bytes(), - vrf_proof: vrf_proof.to_bytes(), - authority_index: *authority_index, - slot_number: *slot_number, - } - }, - PreDigest::Secondary { - authority_index, - slot_number, - } => { - RawPreDigest::Secondary { - authority_index: *authority_index, - slot_number: *slot_number, - } - }, - }; - - codec::Encode::encode(&raw) - } -} - -#[cfg(feature = "std")] -impl codec::EncodeLike for PreDigest {} - -#[cfg(feature = "std")] -impl Decode for PreDigest { - fn decode(i: &mut R) -> Result { - let pre_digest = match Decode::decode(i)? { - RawPreDigest::Primary { vrf_output, vrf_proof, authority_index, slot_number } => { - // Verify (at compile time) that the sizes in babe_primitives are correct - let _: [u8; super::VRF_OUTPUT_LENGTH] = vrf_output; - let _: [u8; super::VRF_PROOF_LENGTH] = vrf_proof; - - PreDigest::Primary { - vrf_proof: VRFProof::from_bytes(&vrf_proof).map_err(convert_error)?, - vrf_output: VRFOutput::from_bytes(&vrf_output).map_err(convert_error)?, - authority_index, - slot_number, - } - }, - RawPreDigest::Secondary { authority_index, slot_number } => { - PreDigest::Secondary { authority_index, slot_number } - }, - }; - - Ok(pre_digest) +impl TryFrom for PreDigest { + type Error = SignatureError; + + fn try_from(raw: RawPreDigest) -> Result { + Ok(match raw { + RawPreDigest::Primary(primary) => PreDigest::Primary(primary.try_into()?), + RawPreDigest::Secondary(secondary) => PreDigest::Secondary(secondary), + }) } } /// Information about the next epoch. This is broadcast in the first block /// of the epoch. -#[derive(Decode, Encode, Default, PartialEq, Eq, Clone, sp_runtime::RuntimeDebug)] +#[derive(Decode, Encode, Default, PartialEq, Eq, Clone, RuntimeDebug)] pub struct NextEpochDescriptor { /// The authorities. pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, /// The value of randomness to use for the slot-assignment. - pub randomness: [u8; VRF_OUTPUT_LENGTH], + pub randomness: Randomness, } /// A digest item which is usable with BABE consensus. @@ -248,34 +191,3 @@ impl CompatibleDigestItem for DigestItem where }) } } - -#[cfg(feature = "std")] -fn convert_error(e: SignatureError) -> codec::Error { - use SignatureError::*; - use MultiSignatureStage::*; - match e { - EquationFalse => "Signature error: `EquationFalse`".into(), - PointDecompressionError => "Signature error: `PointDecompressionError`".into(), - ScalarFormatError => "Signature error: `ScalarFormatError`".into(), - NotMarkedSchnorrkel => "Signature error: `NotMarkedSchnorrkel`".into(), - BytesLengthError { .. } => "Signature error: `BytesLengthError`".into(), - MuSigAbsent { musig_stage: Commitment } => - "Signature error: `MuSigAbsent` at stage `Commitment`".into(), - MuSigAbsent { musig_stage: Reveal } => - "Signature error: `MuSigAbsent` at stage `Reveal`".into(), - MuSigAbsent { musig_stage: Cosignature } => - "Signature error: `MuSigAbsent` at stage `Commitment`".into(), - MuSigInconsistent { musig_stage: Commitment, duplicate: true } => - "Signature error: `MuSigInconsistent` at stage `Commitment` on duplicate".into(), - MuSigInconsistent { musig_stage: Commitment, duplicate: false } => - "Signature error: `MuSigInconsistent` at stage `Commitment` on not duplicate".into(), - MuSigInconsistent { musig_stage: Reveal, duplicate: true } => - "Signature error: `MuSigInconsistent` at stage `Reveal` on duplicate".into(), - MuSigInconsistent { musig_stage: Reveal, duplicate: false } => - "Signature error: `MuSigInconsistent` at stage `Reveal` on not duplicate".into(), - MuSigInconsistent { musig_stage: Cosignature, duplicate: true } => - "Signature error: `MuSigInconsistent` at stage `Cosignature` on duplicate".into(), - MuSigInconsistent { musig_stage: Cosignature, duplicate: false } => - "Signature error: `MuSigInconsistent` at stage `Cosignature` on not duplicate".into(), - } -} diff --git a/primitives/consensus/babe/src/lib.rs b/primitives/consensus/babe/src/lib.rs index 392dcb560bb008202004015ea971dfeddca8b1b4..33701860d1f10234a823c4d625acfc02094f89f5 100644 --- a/primitives/consensus/babe/src/lib.rs +++ b/primitives/consensus/babe/src/lib.rs @@ -22,6 +22,10 @@ pub mod digests; pub mod inherents; +pub use sp_consensus_vrf::schnorrkel::{ + Randomness, VRF_PROOF_LENGTH, VRF_OUTPUT_LENGTH, RANDOMNESS_LENGTH +}; + use codec::{Encode, Decode}; use sp_std::vec::Vec; use sp_runtime::{ConsensusEngineId, RuntimeDebug}; @@ -50,12 +54,6 @@ pub type AuthorityId = app::Public; /// The `ConsensusEngineId` of BABE. pub const BABE_ENGINE_ID: ConsensusEngineId = *b"BABE"; -/// The length of the VRF output -pub const VRF_OUTPUT_LENGTH: usize = 32; - -/// The length of the VRF proof -pub const VRF_PROOF_LENGTH: usize = 64; - /// The length of the public key pub const PUBLIC_KEY_LENGTH: usize = 32; @@ -115,7 +113,7 @@ pub struct BabeConfiguration { pub genesis_authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, /// The randomness for the genesis epoch. - pub randomness: [u8; VRF_OUTPUT_LENGTH], + pub randomness: Randomness, /// Whether this chain should run with secondary slots, which are assigned /// in round-robin manner. diff --git a/primitives/consensus/common/Cargo.toml b/primitives/consensus/common/Cargo.toml index e8d3c0c6118d0c4296a0f93aebdcc6640537ca21..d942d7975adc4d2c66d7d2ac76aa80a80fa5d062 100644 --- a/primitives/consensus/common/Cargo.toml +++ b/primitives/consensus/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-consensus" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -14,16 +14,16 @@ documentation = "https://docs.rs/sp-consensus/" derive_more = "0.99.2" libp2p = { version = "0.16.2", default-features = false } log = "0.4.8" -sp-core = { path= "../../core" , version = "2.0.0-alpha.2"} -sp-inherents = { version = "2.0.0-alpha.2", path = "../../inherents" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../../primitives/state-machine" } +sp-core = { path= "../../core" , version = "2.0.0-alpha.5"} +sp-inherents = { version = "2.0.0-alpha.5", path = "../../inherents" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../../primitives/state-machine" } futures = { version = "0.3.1", features = ["thread-pool"] } futures-timer = "3.0.1" futures-diagnose = "1.0" -sp-std = { version = "2.0.0-alpha.2", path = "../../std" } -sp-version = { version = "2.0.0-alpha.2", path = "../../version" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../runtime" } -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", path = "../../std" } +sp-version = { version = "2.0.0-alpha.5", path = "../../version" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../runtime" } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } parking_lot = "0.10.0" serde = { version = "1.0", features = ["derive"] } @@ -32,3 +32,6 @@ sp-test-primitives = { version = "2.0.0-dev", path = "../../test-primitives" } [features] default = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/consensus/pow/Cargo.toml b/primitives/consensus/pow/Cargo.toml index 4abead3258f81afdec7243f4c374d26281ac7544..5ca60bb2155590fa34e1368ee1758ce7639871aa 100644 --- a/primitives/consensus/pow/Cargo.toml +++ b/primitives/consensus/pow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-consensus-pow" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Primitives for Aura consensus" edition = "2018" @@ -9,11 +9,11 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../api" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../runtime" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../core" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../api" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../runtime" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../core" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } [features] default = ["std"] @@ -24,3 +24,6 @@ std = [ "sp-core/std", "codec/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/consensus/vrf/Cargo.toml b/primitives/consensus/vrf/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..cf194ec38b50ac75b638044936ceca73172309c8 --- /dev/null +++ b/primitives/consensus/vrf/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "sp-consensus-vrf" +version = "0.8.0-alpha.5" +authors = ["Parity Technologies "] +description = "Primitives for VRF based consensus" +edition = "2018" +license = "GPL-3.0" +repository = "https://github.com/paritytech/substrate/" +homepage = "https://substrate.dev" + +[dependencies] +codec = { version = "1.0.0", package = "parity-scale-codec", default-features = false } +schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"], optional = true } +sp-std = { version = "2.0.0-alpha.5", path = "../../std", default-features = false } +sp-core = { version = "2.0.0-alpha.5", path = "../../core", default-features = false } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../runtime" } + +[features] +default = ["std"] +std = [ + "codec/std", + "schnorrkel", + "sp-std/std", + "sp-core/std", + "sp-runtime/std", +] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/consensus/vrf/src/lib.rs b/primitives/consensus/vrf/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..4ec6e376d6829f15937e38d569c9edf1779b0927 --- /dev/null +++ b/primitives/consensus/vrf/src/lib.rs @@ -0,0 +1,20 @@ +// Copyright 2019-2020 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 . + +//! Primitives for VRF-based consensus engines. +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod schnorrkel; diff --git a/primitives/consensus/vrf/src/schnorrkel.rs b/primitives/consensus/vrf/src/schnorrkel.rs new file mode 100644 index 0000000000000000000000000000000000000000..265572dbdaee78dc4848f075bf22a690ea743a4e --- /dev/null +++ b/primitives/consensus/vrf/src/schnorrkel.rs @@ -0,0 +1,250 @@ +// Copyright 2019-2020 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 . + +//! Schnorrkel-based VRF. + +use codec::{Encode, Decode}; +use sp_runtime::RuntimeDebug; +use sp_std::ops::{Deref, DerefMut}; +#[cfg(feature = "std")] +use std::convert::TryFrom; +#[cfg(feature = "std")] +use codec::EncodeLike; +#[cfg(feature = "std")] +use schnorrkel::errors::MultiSignatureStage; +#[cfg(feature = "std")] +use sp_core::U512; + +#[cfg(feature = "std")] +pub use schnorrkel::{SignatureError, vrf::{VRF_PROOF_LENGTH, VRF_OUTPUT_LENGTH}}; + +/// The length of the VRF proof. +#[cfg(not(feature = "std"))] +pub const VRF_PROOF_LENGTH: usize = 64; + +/// The length of the VRF output. +#[cfg(not(feature = "std"))] +pub const VRF_OUTPUT_LENGTH: usize = 32; + +/// The length of the Randomness. +pub const RANDOMNESS_LENGTH: usize = VRF_OUTPUT_LENGTH; + +/// Raw VRF output. +#[derive(Clone, Copy, Eq, PartialEq, RuntimeDebug, Encode, Decode)] +pub struct RawVRFOutput(pub [u8; VRF_OUTPUT_LENGTH]); + +impl Deref for RawVRFOutput { + type Target = [u8; VRF_OUTPUT_LENGTH]; + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl DerefMut for RawVRFOutput { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +/// VRF output type available for `std` environment, suitable for schnorrkel operations. +#[cfg(feature = "std")] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VRFOutput(pub schnorrkel::vrf::VRFOutput); + +#[cfg(feature = "std")] +impl Deref for VRFOutput { + type Target = schnorrkel::vrf::VRFOutput; + fn deref(&self) -> &Self::Target { &self.0 } +} + +#[cfg(feature = "std")] +impl DerefMut for VRFOutput { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +#[cfg(feature = "std")] +impl Encode for VRFOutput { + fn encode(&self) -> Vec { + self.0.as_bytes().encode() + } +} + +#[cfg(feature = "std")] +impl EncodeLike for VRFOutput { } + +#[cfg(feature = "std")] +impl Decode for VRFOutput { + fn decode(i: &mut R) -> Result { + let decoded = <[u8; VRF_OUTPUT_LENGTH]>::decode(i)?; + Ok(Self(schnorrkel::vrf::VRFOutput::from_bytes(&decoded).map_err(convert_error)?)) + } +} + +#[cfg(feature = "std")] +impl TryFrom<[u8; VRF_OUTPUT_LENGTH]> for VRFOutput { + type Error = SignatureError; + + fn try_from(raw: [u8; VRF_OUTPUT_LENGTH]) -> Result { + schnorrkel::vrf::VRFOutput::from_bytes(&raw).map(VRFOutput) + } +} + +#[cfg(feature = "std")] +impl TryFrom for VRFOutput { + type Error = SignatureError; + + fn try_from(raw: RawVRFOutput) -> Result { + schnorrkel::vrf::VRFOutput::from_bytes(&raw.0).map(VRFOutput) + } +} + +#[cfg(feature = "std")] +impl From for RawVRFOutput { + fn from(output: VRFOutput) -> RawVRFOutput { + RawVRFOutput(output.to_bytes()) + } +} + +/// Raw VRF proof. +#[derive(Clone, Copy, Encode, Decode)] +pub struct RawVRFProof(pub [u8; VRF_PROOF_LENGTH]); + +impl Deref for RawVRFProof { + type Target = [u8; VRF_PROOF_LENGTH]; + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl DerefMut for RawVRFProof { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +#[cfg(feature = "std")] +impl std::fmt::Debug for RawVRFProof { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", &self) + } +} + +impl core::cmp::PartialEq for RawVRFProof { + fn eq(&self, other: &Self) -> bool { + self == other + } +} + +impl core::cmp::Eq for RawVRFProof { } + +/// VRF proof type available for `std` environment, suitable for schnorrkel operations. +#[cfg(feature = "std")] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VRFProof(pub schnorrkel::vrf::VRFProof); + +#[cfg(feature = "std")] +impl PartialOrd for VRFProof { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(feature = "std")] +impl Ord for VRFProof { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + U512::from(self.0.to_bytes()).cmp(&U512::from(other.0.to_bytes())) + } +} + +#[cfg(feature = "std")] +impl Deref for VRFProof { + type Target = schnorrkel::vrf::VRFProof; + fn deref(&self) -> &Self::Target { &self.0 } +} + +#[cfg(feature = "std")] +impl DerefMut for VRFProof { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +#[cfg(feature = "std")] +impl Encode for VRFProof { + fn encode(&self) -> Vec { + self.0.to_bytes().encode() + } +} + +#[cfg(feature = "std")] +impl EncodeLike for VRFProof { } + +#[cfg(feature = "std")] +impl Decode for VRFProof { + fn decode(i: &mut R) -> Result { + let decoded = <[u8; VRF_PROOF_LENGTH]>::decode(i)?; + Ok(Self(schnorrkel::vrf::VRFProof::from_bytes(&decoded).map_err(convert_error)?)) + } +} + +#[cfg(feature = "std")] +impl TryFrom<[u8; VRF_PROOF_LENGTH]> for VRFProof { + type Error = SignatureError; + + fn try_from(raw: [u8; VRF_PROOF_LENGTH]) -> Result { + schnorrkel::vrf::VRFProof::from_bytes(&raw).map(VRFProof) + } +} + +#[cfg(feature = "std")] +impl TryFrom for VRFProof { + type Error = SignatureError; + + fn try_from(raw: RawVRFProof) -> Result { + schnorrkel::vrf::VRFProof::from_bytes(&raw.0).map(VRFProof) + } +} + +#[cfg(feature = "std")] +impl From for RawVRFProof { + fn from(output: VRFProof) -> RawVRFProof { + RawVRFProof(output.to_bytes()) + } +} + +#[cfg(feature = "std")] +fn convert_error(e: SignatureError) -> codec::Error { + use SignatureError::*; + use MultiSignatureStage::*; + match e { + EquationFalse => "Signature error: `EquationFalse`".into(), + PointDecompressionError => "Signature error: `PointDecompressionError`".into(), + ScalarFormatError => "Signature error: `ScalarFormatError`".into(), + NotMarkedSchnorrkel => "Signature error: `NotMarkedSchnorrkel`".into(), + BytesLengthError { .. } => "Signature error: `BytesLengthError`".into(), + MuSigAbsent { musig_stage: Commitment } => + "Signature error: `MuSigAbsent` at stage `Commitment`".into(), + MuSigAbsent { musig_stage: Reveal } => + "Signature error: `MuSigAbsent` at stage `Reveal`".into(), + MuSigAbsent { musig_stage: Cosignature } => + "Signature error: `MuSigAbsent` at stage `Commitment`".into(), + MuSigInconsistent { musig_stage: Commitment, duplicate: true } => + "Signature error: `MuSigInconsistent` at stage `Commitment` on duplicate".into(), + MuSigInconsistent { musig_stage: Commitment, duplicate: false } => + "Signature error: `MuSigInconsistent` at stage `Commitment` on not duplicate".into(), + MuSigInconsistent { musig_stage: Reveal, duplicate: true } => + "Signature error: `MuSigInconsistent` at stage `Reveal` on duplicate".into(), + MuSigInconsistent { musig_stage: Reveal, duplicate: false } => + "Signature error: `MuSigInconsistent` at stage `Reveal` on not duplicate".into(), + MuSigInconsistent { musig_stage: Cosignature, duplicate: true } => + "Signature error: `MuSigInconsistent` at stage `Cosignature` on duplicate".into(), + MuSigInconsistent { musig_stage: Cosignature, duplicate: false } => + "Signature error: `MuSigInconsistent` at stage `Cosignature` on not duplicate".into(), + } +} + +/// Schnorrkel randomness value. Same size as `VRFOutput`. +pub type Randomness = [u8; RANDOMNESS_LENGTH]; diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 2b694fea30c023e8acd10f439935c135e3c9a88b..7708188ea4bf230e40a06814e5ac0773773da173 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-core" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,46 +10,45 @@ description = "Shareable Substrate types." documentation = "https://docs.rs/sp-core" [dependencies] -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -rustc-hex = { version = "2.0.1", default-features = false } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } log = { version = "0.4.8", default-features = false } serde = { version = "1.0.101", optional = true, features = ["derive"] } byteorder = { version = "1.3.2", default-features = false } -primitive-types = { version = "0.6.2", default-features = false, features = ["codec"] } +primitive-types = { version = "0.7.0", default-features = false, features = ["codec"] } impl-serde = { version = "0.3.0", optional = true } wasmi = { version = "0.6.2", 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 } rand = { version = "0.7.2", optional = true } -substrate-bip39 = { version = "0.3.1", optional = true } +substrate-bip39 = { version = "0.4.1", optional = true } tiny-bip39 = { version = "0.7", optional = true } regex = { version = "1.3.1", optional = true } num-traits = { version = "0.2.8", default-features = false } zeroize = { version = "1.0.0", default-features = false } lazy_static = { version = "1.4.0", default-features = false, optional = true } parking_lot = { version = "0.10.0", optional = true } -sp-debug-derive = { version = "2.0.0-alpha.2", path = "../debug-derive" } -sp-externalities = { version = "0.8.0-alpha.2", optional = true, path = "../externalities" } -sp-storage = { version = "2.0.0-alpha.2", default-features = false, path = "../storage" } -parity-util-mem = { version = "0.5.2", default-features = false, features = ["primitive-types"] } +sp-debug-derive = { version = "2.0.0-alpha.5", path = "../debug-derive" } +sp-externalities = { version = "0.8.0-alpha.5", optional = true, path = "../externalities" } +sp-storage = { version = "2.0.0-alpha.5", default-features = false, path = "../storage" } +parity-util-mem = { version = "0.6.0", default-features = false, features = ["primitive-types"] } futures = { version = "0.3.1", optional = true } # full crypto ed25519-dalek = { version = "1.0.0-pre.3", 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 } -schnorrkel = { version = "0.8.5", features = ["preaudit_deprecated", "u64_backend"], default-features = false, optional = true } +schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false, optional = true } sha2 = { version = "0.8.0", 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.3.2", default-features = false, features = ["hmac"], optional = true } -sp-runtime-interface = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime-interface" } +sp-runtime-interface = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime-interface" } [dev-dependencies] -sp-serializer = { version = "2.0.0-alpha.2", path = "../serializer" } +sp-serializer = { version = "2.0.0-alpha.5", path = "../serializer" } pretty_assertions = "0.6.1" hex-literal = "0.2.1" rand = "0.7.2" @@ -75,14 +74,12 @@ std = [ "primitive-types/serde", "primitive-types/byteorder", "primitive-types/rustc-hex", - "primitive-types/libc", "impl-serde", "codec/std", "hash256-std-hasher/std", "hash-db/std", "sp-std/std", "serde", - "rustc-hex/std", "twox-hash/std", "blake2-rfc/std", "ed25519-dalek/std", @@ -122,3 +119,6 @@ full_crypto = [ "libsecp256k1", "sp-runtime-interface/disable_target_static_assertions", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index 259c3c5f9a7043425aaccbe708bcea4d3c0df225..f16c1609603c9f9726721c19d7f7e8384ae762ac 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -18,8 +18,10 @@ //! Cryptographic utilities. // end::description[] +use crate::{sr25519, ed25519}; use sp_std::hash::Hash; use sp_std::vec::Vec; +use sp_std::str; #[cfg(feature = "std")] use sp_std::convert::TryInto; use sp_std::convert::TryFrom; @@ -32,7 +34,8 @@ use codec::{Encode, Decode}; use regex::Regex; #[cfg(feature = "std")] use base58::{FromBase58, ToBase58}; - +#[cfg(feature = "std")] +use crate::hexdisplay::HexDisplay; use zeroize::Zeroize; #[doc(hidden)] pub use sp_std::ops::Deref; @@ -538,7 +541,9 @@ 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 { +pub trait Public: + AsRef<[u8]> + AsMut<[u8]> + Default + Derive + CryptoType + PartialEq + Eq + Clone + Send + Sync +{ /// A new instance from the given slice. /// /// NOTE: No checking goes on to ensure this is a real public key. Only use it if @@ -554,6 +559,7 @@ pub trait Public: AsRef<[u8]> + AsMut<[u8]> + Default + Derive + CryptoType + Pa /// An opaque 32-byte cryptographic identifier. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Default, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Hash))] pub struct AccountId32([u8; 32]); impl UncheckedFrom for AccountId32 { @@ -614,6 +620,18 @@ impl From for [u8; 32] { } } +impl From for AccountId32 { + fn from(k: sr25519::Public) -> Self { + k.0.into() + } +} + +impl From for AccountId32 { + fn from(k: ed25519::Public) -> Self { + k.0.into() + } +} + #[cfg(feature = "std")] impl std::fmt::Display for AccountId32 { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { @@ -941,6 +959,27 @@ impl<'a> TryFrom<&'a str> for KeyTypeId { } } +/// An identifier for a specific cryptographic algorithm used by a key pair +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)] +pub struct CryptoTypeId(pub [u8; 4]); + +/// A type alias of CryptoTypeId & a public key +#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)] +pub struct CryptoTypePublicPair(pub CryptoTypeId, pub Vec); + +#[cfg(feature = "std")] +impl sp_std::fmt::Display for CryptoTypePublicPair { + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + let id = match str::from_utf8(&(self.0).0[..]) { + Ok(id) => id.to_string(), + Err(_) => { + format!("{:#?}", self.0) + } + }; + write!(f, "{}-{}", id, HexDisplay::from(&self.1)) + } +} + /// Known key types; this also functions as a global registry of key types for projects wishing to /// avoid collisions with each other. /// @@ -961,6 +1000,8 @@ pub mod key_types { pub const IM_ONLINE: KeyTypeId = KeyTypeId(*b"imon"); /// Key type for AuthorityDiscovery module, built-in. pub const AUTHORITY_DISCOVERY: KeyTypeId = KeyTypeId(*b"audi"); + /// Key type for staking, built-in. + pub const STAKING: KeyTypeId = KeyTypeId(*b"stak"); /// A key type ID useful for tests. pub const DUMMY: KeyTypeId = KeyTypeId(*b"dumy"); } diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 548ae49b55487d31779a150909456beb74be04c8..8a45157844f3af20ce5ded4ce3dc28f6008a5983 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -36,10 +36,13 @@ use crate::{hashing::blake2_256, crypto::{Pair as TraitPair, DeriveJunction, Sec use crate::crypto::Ss58Codec; #[cfg(feature = "std")] use serde::{de, Serializer, Serialize, Deserializer, Deserialize}; -use crate::crypto::{Public as TraitPublic, UncheckedFrom, CryptoType, Derive}; +use crate::crypto::{Public as TraitPublic, UncheckedFrom, CryptoType, Derive, CryptoTypeId}; #[cfg(feature = "full_crypto")] use secp256k1::{PublicKey, SecretKey}; +/// An identifier used to match public keys against ecdsa keys +pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds"); + /// A secret seed (which is bytewise essentially equivalent to a SecretKey). /// /// We need it as a different type because `Seed` is expected to be AsRef<[u8]>. diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index 5e04dcceffb395fe5433bfcb28a217f8c44f06b5..abeac05388d2773e7ba80f60d6b4e9193ebe2fcd 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -38,10 +38,13 @@ use crate::crypto::{Pair as TraitPair, DeriveJunction, SecretStringError}; use crate::crypto::Ss58Codec; #[cfg(feature = "std")] use serde::{de, Serializer, Serialize, Deserializer, Deserialize}; -use crate::{crypto::{Public as TraitPublic, UncheckedFrom, CryptoType, Derive}}; +use crate::crypto::{Public as TraitPublic, CryptoTypePublicPair, UncheckedFrom, CryptoType, Derive, CryptoTypeId}; use sp_runtime_interface::pass_by::PassByInner; use sp_std::ops::Deref; +/// An identifier used to match public keys against ed25519 keys +pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ed25"); + /// A secret seed. It's not called a "secret key" because ring doesn't expose the secret keys /// of the key pair (yeah, dumb); as such we're forced to remember the seed manually if we /// will need it later (such as for HDKD). @@ -378,6 +381,18 @@ impl TraitPublic for Public { impl Derive for Public {} +impl From for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } +} + +impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) + } +} + /// Derive a single hard junction. #[cfg(feature = "full_crypto")] fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index 79380a6fc65321dcd9e86e3d8e46200e9afc0ffc..8d5ad7daaec83965e26ac78b68219f6e41da80e1 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -74,7 +74,7 @@ pub mod testing; pub mod tasks; pub use self::hash::{H160, H256, H512, convert_hash}; -pub use self::uint::U256; +pub use self::uint::{U256, U512}; pub use changes_trie::{ChangesTrieConfiguration, ChangesTrieConfigurationRange}; #[cfg(feature = "full_crypto")] pub use crypto::{DeriveJunction, Pair, Public}; diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index 3495f32872f1694abe6756b643acb3ab7b7d3891..717952eb01c71b7cb398c5c57efc040704e07d24 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -39,7 +39,7 @@ use crate::crypto::{ #[cfg(feature = "std")] use crate::crypto::Ss58Codec; -use crate::{crypto::{Public as TraitPublic, UncheckedFrom, CryptoType, Derive}}; +use crate::crypto::{Public as TraitPublic, CryptoTypePublicPair, UncheckedFrom, CryptoType, Derive, CryptoTypeId}; use crate::hash::{H256, H512}; use codec::{Encode, Decode}; use sp_std::ops::Deref; @@ -54,6 +54,9 @@ use sp_runtime_interface::pass_by::PassByInner; #[cfg(feature = "full_crypto")] const SIGNING_CTX: &[u8] = b"substrate"; +/// An identifier used to match public keys against sr25519 keys +pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"sr25"); + /// An Schnorrkel/Ristretto x25519 ("sr25519") public key. #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Encode, Decode, Default, PassByInner)] @@ -390,6 +393,18 @@ impl TraitPublic for Public { } } +impl From for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } +} + +impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) + } +} + #[cfg(feature = "std")] impl From for Pair { fn from(sec: MiniSecretKey) -> Pair { @@ -529,25 +544,24 @@ impl TraitPair for Pair { self.0.sign(context.bytes(message)).into() } - /// Verify a signature on a message. Returns true if the signature is good. fn verify>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool { Self::verify_weak(&sig.0[..], message, pubkey) } - /// Verify a signature on a message. Returns true if the signature is good. fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { - // Match both schnorrkel 0.1.1 and 0.8.0+ signatures, supporting both wallets - // that have not been upgraded and those that have. To swap to 0.8.0 only, - // create `schnorrkel::Signature` and pass that into `verify_simple` - match PublicKey::from_bytes(pubkey.as_ref()) { - Ok(pk) => pk.verify_simple_preaudit_deprecated( - SIGNING_CTX, message.as_ref(), &sig, - ).is_ok(), - Err(_) => false, - } + let signature = match schnorrkel::Signature::from_bytes(sig) { + Ok(signature) => signature, + Err(_) => return false, + }; + + let pub_key = match PublicKey::from_bytes(pubkey.as_ref()) { + Ok(pub_key) => pub_key, + Err(_) => return false, + }; + + pub_key.verify_simple(SIGNING_CTX, message.as_ref(), &signature).is_ok() } - /// Return a vec filled with raw data. fn to_raw_vec(&self) -> Vec { self.0.secret.to_bytes().to_vec() } @@ -566,6 +580,20 @@ impl Pair { let kp = mini_key.expand_to_keypair(ExpansionMode::Ed25519); (Pair(kp), mini_key.to_bytes()) } + + /// Verify a signature on a message. Returns `true` if the signature is good. + /// Supports old 0.1.1 deprecated signatures and should be used only for backward + /// compatibility. + pub fn verify_deprecated>(sig: &Signature, message: M, pubkey: &Public) -> bool { + // Match both schnorrkel 0.1.1 and 0.8.0+ signatures, supporting both wallets + // that have not been upgraded and those that have. + match PublicKey::from_bytes(pubkey.as_ref()) { + Ok(pk) => pk.verify_simple_preaudit_deprecated( + SIGNING_CTX, message.as_ref(), &sig.0[..], + ).is_ok(), + Err(_) => false, + } + } } impl CryptoType for Public { @@ -586,7 +614,7 @@ impl CryptoType for Pair { #[cfg(test)] mod compatibility_test { use super::*; - use crate::crypto::{DEV_PHRASE}; + 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 @@ -609,14 +637,15 @@ mod compatibility_test { } #[test] - fn verify_known_message_should_work() { + fn verify_known_old_message_should_work() { let public = Public::from_raw(hex!("b4bfa1f7a5166695eb75299fd1c4c03ea212871c342f2c5dfea0902b2c246918")); // signature generated by the 1.1 version with the same ^^ public key. let signature = Signature::from_raw(hex!( "5a9755f069939f45d96aaf125cf5ce7ba1db998686f87f2fb3cbdea922078741a73891ba265f70c31436e18a9acd14d189d73c12317ab6c313285cd938453202" )); let message = b"Verifying that I am the owner of 5G9hQLdsKQswNPgB499DeA5PkFBbgkLPJWkkS6FAM6xGQ8xD. Hash: 221455a3\n"; - assert!(Pair::verify(&signature, &message[..], &public)); + assert!(Pair::verify_deprecated(&signature, &message[..], &public)); + assert!(!Pair::verify(&signature, &message[..], &public)); } } @@ -776,7 +805,7 @@ mod test { } #[test] - fn verify_from_wasm_works() { + fn verify_from_old_wasm_works() { // The values in this test case are compared to the output of `node-test.js` in schnorrkel-js. // // This is to make sure that the wasm library is compatible. @@ -787,7 +816,8 @@ mod test { let js_signature = Signature::from_raw(hex!( "28a854d54903e056f89581c691c1f7d2ff39f8f896c9e9c22475e60902cc2b3547199e0e91fa32902028f2ca2355e8cdd16cfe19ba5e8b658c94aa80f3b81a00" )); - assert!(Pair::verify(&js_signature, b"SUBSTRATE", &public)); + assert!(Pair::verify_deprecated(&js_signature, b"SUBSTRATE", &public)); + assert!(!Pair::verify(&js_signature, b"SUBSTRATE", &public)); } #[test] diff --git a/primitives/core/src/testing.rs b/primitives/core/src/testing.rs index 247323f9703b41ecf5a22897da4e4b0c6de1fb12..b5e6f4c7aff37427573de40587b9bfe6bf311281 100644 --- a/primitives/core/src/testing.rs +++ b/primitives/core/src/testing.rs @@ -16,10 +16,16 @@ //! Types that should only be used for testing! +use crate::crypto::{KeyTypeId, CryptoTypePublicPair}; #[cfg(feature = "std")] -use crate::{ed25519, sr25519, crypto::{Public, Pair}}; -use crate::crypto::KeyTypeId; - +use crate::{ + crypto::{Pair, Public}, + ed25519, sr25519, + traits::BareCryptoStoreError +}; +#[cfg(feature = "std")] +use std::collections::HashSet; +use codec::Encode; /// Key type for generic Ed25519 key. pub const ED25519: KeyTypeId = KeyTypeId(*b"ed25"); /// Key type for generic Sr 25519 key. @@ -39,10 +45,41 @@ impl KeyStore { pub fn new() -> crate::traits::BareCryptoStorePtr { std::sync::Arc::new(parking_lot::RwLock::new(Self::default())) } + + fn sr25519_key_pair(&self, id: KeyTypeId, pub_key: &sr25519::Public) -> Option { + self.keys.get(&id) + .and_then(|inner| + inner.get(pub_key.as_slice()) + .map(|s| sr25519::Pair::from_string(s, None).expect("`sr25519` seed slice is valid")) + ) + } + + fn ed25519_key_pair(&self, id: KeyTypeId, pub_key: &ed25519::Public) -> Option { + self.keys.get(&id) + .and_then(|inner| + inner.get(pub_key.as_slice()) + .map(|s| ed25519::Pair::from_string(s, None).expect("`ed25519` seed slice is valid")) + ) + } + } #[cfg(feature = "std")] impl crate::traits::BareCryptoStore for KeyStore { + fn keys(&self, id: KeyTypeId) -> Result, BareCryptoStoreError> { + self.keys + .get(&id) + .map(|map| { + Ok(map.keys() + .fold(Vec::new(), |mut v, k| { + v.push(CryptoTypePublicPair(sr25519::CRYPTO_ID, k.clone())); + v.push(CryptoTypePublicPair(ed25519::CRYPTO_ID, k.clone())); + v + })) + }) + .unwrap_or(Ok(vec![])) + } + fn sr25519_public_keys(&self, id: KeyTypeId) -> Vec { self.keys.get(&id) .map(|keys| @@ -58,10 +95,11 @@ impl crate::traits::BareCryptoStore for KeyStore { &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> Result { + ) -> Result { match seed { Some(seed) => { - let pair = sr25519::Pair::from_string(seed, None).expect("Generates an `sr25519` pair."); + let pair = sr25519::Pair::from_string(seed, None) + .map_err(|_| BareCryptoStoreError::ValidationError("Generates an `sr25519` pair.".to_owned()))?; self.keys.entry(id).or_default().insert(pair.public().to_raw_vec(), seed.into()); Ok(pair.public()) }, @@ -73,14 +111,6 @@ impl crate::traits::BareCryptoStore for KeyStore { } } - fn sr25519_key_pair(&self, id: KeyTypeId, pub_key: &sr25519::Public) -> Option { - self.keys.get(&id) - .and_then(|inner| - inner.get(pub_key.as_slice()) - .map(|s| sr25519::Pair::from_string(s, None).expect("`sr25519` seed slice is valid")) - ) - } - fn ed25519_public_keys(&self, id: KeyTypeId) -> Vec { self.keys.get(&id) .map(|keys| @@ -96,10 +126,11 @@ impl crate::traits::BareCryptoStore for KeyStore { &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> Result { + ) -> Result { match seed { Some(seed) => { - let pair = ed25519::Pair::from_string(seed, None).expect("Generates an `ed25519` pair."); + let pair = ed25519::Pair::from_string(seed, None) + .map_err(|_| BareCryptoStoreError::ValidationError("Generates an `ed25519` pair.".to_owned()))?; self.keys.entry(id).or_default().insert(pair.public().to_raw_vec(), seed.into()); Ok(pair.public()) }, @@ -111,14 +142,6 @@ impl crate::traits::BareCryptoStore for KeyStore { } } - fn ed25519_key_pair(&self, id: KeyTypeId, pub_key: &ed25519::Public) -> Option { - self.keys.get(&id) - .and_then(|inner| - inner.get(pub_key.as_slice()) - .map(|s| ed25519::Pair::from_string(s, None).expect("`ed25519` seed slice is valid")) - ) - } - fn insert_unknown(&mut self, id: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { self.keys.entry(id).or_default().insert(public.to_owned(), suri.to_string()); Ok(()) @@ -131,6 +154,40 @@ impl crate::traits::BareCryptoStore for KeyStore { fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { public_keys.iter().all(|(k, t)| self.keys.get(&t).and_then(|s| s.get(k)).is_some()) } + + fn supported_keys( + &self, + id: KeyTypeId, + keys: Vec, + ) -> std::result::Result, BareCryptoStoreError> { + let provided_keys = keys.into_iter().collect::>(); + let all_keys = self.keys(id)?.into_iter().collect::>(); + + Ok(provided_keys.intersection(&all_keys).cloned().collect()) + } + + fn sign_with( + &self, + id: KeyTypeId, + key: &CryptoTypePublicPair, + msg: &[u8], + ) -> Result, BareCryptoStoreError> { + match key.0 { + ed25519::CRYPTO_ID => { + let key_pair: ed25519::Pair = self + .ed25519_key_pair(id, &ed25519::Public::from_slice(key.1.as_slice())) + .ok_or(BareCryptoStoreError::PairNotFound("ed25519".to_owned()))?; + return Ok(key_pair.sign(msg).encode()); + } + sr25519::CRYPTO_ID => { + let key_pair: sr25519::Pair = self + .sr25519_key_pair(id, &sr25519::Public::from_slice(key.1.as_slice())) + .ok_or(BareCryptoStoreError::PairNotFound("sr25519".to_owned()))?; + return Ok(key_pair.sign(msg).encode()); + } + _ => Err(BareCryptoStoreError::KeyNotSupported(id)) + } + } } /// Macro for exporting functions from wasm in with the expected signature for using it with the @@ -247,11 +304,9 @@ mod tests { .ed25519_generate_new(ED25519, None) .expect("Generates key"); - let store_key_pair = store.read() - .ed25519_key_pair(ED25519, &public) - .expect("Key should exists in store"); + let public_keys = store.read().keys(ED25519).unwrap(); - assert_eq!(public, store_key_pair.public()); + assert!(public_keys.contains(&public.into())); } #[test] @@ -267,11 +322,8 @@ mod tests { key_pair.public().as_ref(), ).expect("Inserts unknown key"); - let store_key_pair = store.read().sr25519_key_pair( - SR25519, - &key_pair.public(), - ).expect("Gets key pair from keystore"); + let public_keys = store.read().keys(SR25519).unwrap(); - assert_eq!(key_pair.public(), store_key_pair.public()); + assert!(public_keys.contains(&key_pair.public().into())); } } diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index 21a0b77ca7ab4ca0d7345928bc03c2d2e569a674..14839fb58562a3ec1bcff7219e6dbfb7e1b47b4d 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -16,14 +16,35 @@ //! Shareable Substrate traits. -use crate::{crypto::KeyTypeId, ed25519, sr25519}; +use crate::{ + crypto::{KeyTypeId, CryptoTypePublicPair}, + ed25519, sr25519, +}; use std::{ - fmt::{Debug, Display}, panic::UnwindSafe, sync::Arc, borrow::Cow, + borrow::Cow, + fmt::{Debug, Display}, + panic::UnwindSafe, + sync::Arc, }; pub use sp_externalities::{Externalities, ExternalitiesExt}; +/// BareCryptoStore error +#[derive(Debug)] +pub enum BareCryptoStoreError { + /// Public key type is not supported + KeyNotSupported(KeyTypeId), + /// Pair not found for public key and KeyTypeId + PairNotFound(String), + /// Validation error + ValidationError(String), + /// Keystore unavailable + Unavailable, + /// Programming errors + Other(String) +} + /// Something that generates, stores and provides access to keys. pub trait BareCryptoStore: Send + Sync { /// Returns all sr25519 public keys for the given key type. @@ -37,10 +58,7 @@ pub trait BareCryptoStore: Send + Sync { &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> Result; - /// Returns the sr25519 key pair for the given key type and public key combination. - fn sr25519_key_pair(&self, id: KeyTypeId, pub_key: &sr25519::Public) -> Option; - + ) -> Result; /// Returns all ed25519 public keys for the given key type. fn ed25519_public_keys(&self, id: KeyTypeId) -> Vec; /// Generate a new ed25519 key pair for the given key type and an optional seed. @@ -52,10 +70,7 @@ pub trait BareCryptoStore: Send + Sync { &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> Result; - - /// Returns the ed25519 key pair for the given key type and public key combination. - fn ed25519_key_pair(&self, id: KeyTypeId, pub_key: &ed25519::Public) -> Option; + ) -> Result; /// Insert a new key. This doesn't require any known of the crypto; but a public key must be /// manually provided. @@ -67,11 +82,78 @@ pub trait BareCryptoStore: Send + Sync { /// Get the password for this store. fn password(&self) -> Option<&str>; + /// Find intersection between provided keys and supported keys + /// + /// Provided a list of (CryptoTypeId,[u8]) pairs, this would return + /// a filtered set of public keys which are supported by the keystore. + fn supported_keys( + &self, + id: KeyTypeId, + keys: Vec + ) -> Result, BareCryptoStoreError>; + /// List all supported keys + /// + /// Returns a set of public keys the signer supports. + fn keys(&self, id: KeyTypeId) -> Result, BareCryptoStoreError>; /// Checks if the private keys for the given public key and key type combinations exist. /// /// Returns `true` iff all private keys could be found. fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool; + + /// Sign with key + /// + /// Signs a message with the private key that matches + /// the public key passed. + /// + /// Returns the SCALE encoded signature if key is found & supported, + /// an error otherwise. + fn sign_with( + &self, + id: KeyTypeId, + key: &CryptoTypePublicPair, + msg: &[u8], + ) -> Result, BareCryptoStoreError>; + + /// Sign with any key + /// + /// Given a list of public keys, find the first supported key and + /// sign the provided message with that key. + /// + /// Returns a tuple of the used key and the signature + fn sign_with_any( + &self, + id: KeyTypeId, + keys: Vec, + msg: &[u8] + ) -> Result<(CryptoTypePublicPair, Vec), BareCryptoStoreError> { + if keys.len() == 1 { + return self.sign_with(id, &keys[0], msg).map(|s| (keys[0].clone(), s)); + } else { + for k in self.supported_keys(id, keys)? { + if let Ok(sign) = self.sign_with(id, &k, msg) { + return Ok((k, sign)); + } + } + } + Err(BareCryptoStoreError::KeyNotSupported(id)) + } + + /// Sign with all keys + /// + /// Provided a list of public keys, sign a message with + /// each key given that the key is supported. + /// + /// Returns a list of `Result`s each representing the signature of each key or + /// a BareCryptoStoreError for non-supported keys. + fn sign_with_all( + &self, + id: KeyTypeId, + keys: Vec, + msg: &[u8], + ) -> Result, BareCryptoStoreError>>, ()>{ + Ok(keys.iter().map(|k| self.sign_with(id, k, msg)).collect()) + } } /// A pointer to the key store. diff --git a/primitives/core/src/uint.rs b/primitives/core/src/uint.rs index 54ed7ca317f001e06dd6c3e7edc0d9fa0029352f..e666137c08161d897707def5fadde55a9d2f1abe 100644 --- a/primitives/core/src/uint.rs +++ b/primitives/core/src/uint.rs @@ -16,7 +16,7 @@ //! An unsigned fixed-size integer. -pub use primitive_types::U256; +pub use primitive_types::{U256, U512}; #[cfg(test)] mod tests { diff --git a/primitives/debug-derive/Cargo.toml b/primitives/debug-derive/Cargo.toml index 1ffa37308e234b1cfb2c9dd36ff8ebba0e2433ee..0079b6219f5b618c709c322cf5639b9f6eec85f9 100644 --- a/primitives/debug-derive/Cargo.toml +++ b/primitives/debug-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-debug-derive" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -22,3 +22,6 @@ std = [] [dev-dependencies] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/externalities/Cargo.toml b/primitives/externalities/Cargo.toml index dc47515dcc1adf4150738d40cd3ff517ca4fe22d..af6e516fbfcd32eec31dd9d446785a5028a70ddd 100644 --- a/primitives/externalities/Cargo.toml +++ b/primitives/externalities/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-externalities" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" license = "GPL-3.0" authors = ["Parity Technologies "] edition = "2018" @@ -10,6 +10,9 @@ description = "Substrate externalities abstraction" documentation = "https://docs.rs/sp-externalities" [dependencies] -sp-storage = { version = "2.0.0-alpha.2", path = "../storage" } -sp-std = { version = "2.0.0-alpha.2", path = "../std" } -environmental = { version = "1.1.1" } \ No newline at end of file +sp-storage = { version = "2.0.0-alpha.5", path = "../storage" } +sp-std = { version = "2.0.0-alpha.5", path = "../std" } +environmental = { version = "1.1.1" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index d36f0e4b527207ddb716b48aef496b184ca5b05b..0595fa7ba700649fa2c4df5ae1ad3178648fdc1f 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-finality-grandpa" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,12 +11,12 @@ documentation = "https://docs.rs/sp-finality-grandpa" [dependencies] -sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../application-crypto" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } +sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../application-crypto" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } serde = { version = "1.0.101", optional = true, features = ["derive"] } -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../api" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../api" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } [features] default = ["std"] @@ -28,3 +28,6 @@ std = [ "sp-api/std", "sp-runtime/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/finality-tracker/Cargo.toml b/primitives/finality-tracker/Cargo.toml index f89cb24d4a74cb3dc5871e1c4f64b4d6862d4dfe..4e6cf6c92d324c36278c18ef0b5b5703b21b56e3 100644 --- a/primitives/finality-tracker/Cargo.toml +++ b/primitives/finality-tracker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-finality-tracker" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,9 +9,9 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME module that tracks the last finalized block, as perceived by block authors." [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/inherents" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } [features] default = ["std"] @@ -20,3 +20,6 @@ std = [ "sp-std/std", "sp-inherents/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/inherents/Cargo.toml b/primitives/inherents/Cargo.toml index 839edba73de92a1ae2c13bf2fa10961be3c36d65..dd640f00ec1a272d87511bd2f22f88916abeebbb 100644 --- a/primitives/inherents/Cargo.toml +++ b/primitives/inherents/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-inherents" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,9 +12,9 @@ documentation = "https://docs.rs/sp-inherents" [dependencies] parking_lot = { version = "0.10.0", optional = true } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../core" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../core" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } derive_more = { version = "0.99.2", optional = true } [features] @@ -26,3 +26,6 @@ std = [ "sp-core/std", "derive_more", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 4f740638024bcaf8974bfc6830f904d47de7271b..9cda2120cc2b45290da425f14f758ba483445b43 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-io" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,16 +11,16 @@ documentation = "https://docs.rs/sp-io" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } hash-db = { version = "0.15.2", default-features = false } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } libsecp256k1 = { version = "0.3.4", optional = true } -sp-state-machine = { version = "0.8.0-alpha.2", optional = true, path = "../../primitives/state-machine" } -sp-wasm-interface = { version = "2.0.0-alpha.2", path = "../../primitives/wasm-interface", default-features = false } -sp-runtime-interface = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime-interface" } -sp-trie = { version = "2.0.0-alpha.2", optional = true, path = "../../primitives/trie" } -sp-externalities = { version = "0.8.0-alpha.2", optional = true, path = "../externalities" } +sp-state-machine = { version = "0.8.0-alpha.5", optional = true, path = "../../primitives/state-machine" } +sp-wasm-interface = { version = "2.0.0-alpha.5", path = "../../primitives/wasm-interface", default-features = false } +sp-runtime-interface = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime-interface" } +sp-trie = { version = "2.0.0-alpha.5", optional = true, path = "../../primitives/trie" } +sp-externalities = { version = "0.8.0-alpha.5", optional = true, path = "../externalities" } log = { version = "0.4.8", optional = true } [features] @@ -46,3 +46,6 @@ std = [ disable_panic_handler = [] disable_oom = [] disable_allocator = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 2702be02071af0def98a5aae5f45e1583df7583a..bc49df159eb6f873d5a9ac7e011a529cc2422cc0 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -286,7 +286,8 @@ pub trait Storage { /// /// The hashing algorithm is defined by the `Block`. /// - /// Returns an `Option` that holds the SCALE encoded hash. + /// Returns an `Some(_)` 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`.") @@ -410,8 +411,9 @@ pub trait Crypto { self.extension::() .expect("No `keystore` associated for the current context!") .read() - .ed25519_key_pair(id, &pub_key) - .map(|k| k.sign(msg)) + .sign_with(id, &pub_key.into(), msg) + .map(|sig| ed25519::Signature::from_slice(sig.as_slice())) + .ok() } /// Verify an `ed25519` signature. @@ -462,13 +464,23 @@ pub trait Crypto { self.extension::() .expect("No `keystore` associated for the current context!") .read() - .sr25519_key_pair(id, &pub_key) - .map(|k| k.sign(msg)) + .sign_with(id, &pub_key.into(), msg) + .map(|sig| sr25519::Signature::from_slice(sig.as_slice())) + .ok() + } + + /// Verify an `sr25519` signature. + /// + /// Returns `true` when the verification in successful regardless of + /// signature version. + fn sr25519_verify(sig: &sr25519::Signature, msg: &[u8], pubkey: &sr25519::Public) -> bool { + sr25519::Pair::verify_deprecated(sig, msg, pubkey) } /// Verify an `sr25519` signature. /// /// Returns `true` when the verification in successful. + #[version(2)] fn sr25519_verify(sig: &sr25519::Signature, msg: &[u8], pubkey: &sr25519::Public) -> bool { sr25519::Pair::verify(sig, msg, pubkey) } diff --git a/primitives/keyring/Cargo.toml b/primitives/keyring/Cargo.toml index 1ec4ebe547f360a803dfa2b64e89427add103df0..0764146250446c367c0c0bfbd53147e0a9dd4134 100644 --- a/primitives/keyring/Cargo.toml +++ b/primitives/keyring/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-keyring" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,7 +11,10 @@ documentation = "https://docs.rs/sp-keyring" [dependencies] -sp-core = { version = "2.0.0-alpha.2", path = "../core" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../runtime" } +sp-core = { version = "2.0.0-alpha.5", path = "../core" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../runtime" } lazy_static = "1.4.0" strum = { version = "0.16.0", features = ["derive"] } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/offchain/Cargo.toml b/primitives/offchain/Cargo.toml index 45324d368b94428540b20202d1daf6dc8f20bcae..66febccd59b9b6ac5a9111cfbd4fcb8027e8113a 100644 --- a/primitives/offchain/Cargo.toml +++ b/primitives/offchain/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Substrate offchain workers primitives" name = "sp-offchain" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" license = "GPL-3.0" authors = ["Parity Technologies "] edition = "2018" @@ -9,8 +9,8 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../api" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../api" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } [features] default = ["std"] @@ -18,3 +18,6 @@ std = [ "sp-api/std", "sp-runtime/std" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/panic-handler/Cargo.toml b/primitives/panic-handler/Cargo.toml index 592107b84f06a4f6d789f27e152b2a7b2c393caf..169443f6c42cee9f40de81baeb04fe5d49a8ba76 100644 --- a/primitives/panic-handler/Cargo.toml +++ b/primitives/panic-handler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-panic-handler" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,3 +12,6 @@ documentation = "https://docs.rs/sp-panic-handler" [dependencies] backtrace = "0.3.38" log = "0.4.8" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/phragmen/Cargo.toml b/primitives/phragmen/Cargo.toml index 6a599bdabd830a746c17a595c2299dbbfcf7c339..f5d26e8a40b80427360bfd56915a540d84d584bc 100644 --- a/primitives/phragmen/Cargo.toml +++ b/primitives/phragmen/Cargo.toml @@ -1,27 +1,34 @@ [package] name = "sp-phragmen" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" -description = "PHRAGMENT primitives" +description = "Phragmen primitives" [dependencies] +codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } serde = { version = "1.0.101", optional = true, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-phragmen-compact = { version = "2.0.0-alpha.4", path = "./compact" } [dev-dependencies] -substrate-test-utils = { version = "2.0.0-alpha.2", path = "../../test-utils" } -sp-io ={ version = "2.0.0-alpha.2", path = "../../primitives/io" } -rand = "0.7.2" +substrate-test-utils = { version = "2.0.0-alpha.5", path = "../../test-utils" } +rand = "0.7.3" +sp-phragmen = { path = "." } [features] default = ["std"] +bench = [] std = [ + "codec/std", "serde", "sp-std/std", "sp-runtime/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/phragmen/benches/phragmen.rs b/primitives/phragmen/benches/phragmen.rs index aa99e6f384ba65fd1d4621d3a3e608cb632e1a12..33da0b19563a8d3f7ef226077862558b5e0fd285 100644 --- a/primitives/phragmen/benches/phragmen.rs +++ b/primitives/phragmen/benches/phragmen.rs @@ -16,6 +16,7 @@ //! Note that execution times will not be accurate in an absolute scale, since //! - Everything is executed in the context of `TestExternalities` //! - Everything is executed in native environment. + #![cfg(feature = "bench")] #![feature(test)] diff --git a/primitives/phragmen/compact/Cargo.toml b/primitives/phragmen/compact/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..56b4520c54277825b7dc340b8fef7a9f132ca43a --- /dev/null +++ b/primitives/phragmen/compact/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sp-phragmen-compact" +version = "2.0.0-dev" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "Phragmen Compact Solution" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0.7", features = ["full", "visit"] } +quote = "1.0" +proc-macro2 = "1.0.6" +proc-macro-crate = "0.1.4" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/phragmen/compact/src/assignment.rs b/primitives/phragmen/compact/src/assignment.rs new file mode 100644 index 0000000000000000000000000000000000000000..587e482ccb22096182404dd04823c05749d4575b --- /dev/null +++ b/primitives/phragmen/compact/src/assignment.rs @@ -0,0 +1,210 @@ +// Copyright 2020 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 . + +//! Code generation for the ratio assignment type. + +use crate::field_name_for; +use proc_macro2::{TokenStream as TokenStream2}; +use syn::{GenericArgument}; +use quote::quote; + +fn from_impl(count: usize) -> TokenStream2 { + let from_impl_single = { + let name = field_name_for(1); + quote!(1 => compact.#name.push( + ( + index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, + index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ) + ),) + }; + + let from_impl_double = { + let name = field_name_for(2); + quote!(2 => compact.#name.push( + ( + index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ( + index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + distribution[0].1, + ), + index_of_target(&distribution[1].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ) + ),) + }; + + let from_impl_rest = (3..=count).map(|c| { + let inner = (0..c-1).map(|i| + quote!((index_of_target(&distribution[#i].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, distribution[#i].1),) + ).collect::(); + + let field_name = field_name_for(c); + let last_index = c - 1; + let last = quote!(index_of_target(&distribution[#last_index].0).ok_or(_phragmen::Error::CompactInvalidIndex)?); + + quote!( + #c => compact.#field_name.push((index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, [#inner], #last)), + ) + }).collect::(); + + quote!( + #from_impl_single + #from_impl_double + #from_impl_rest + ) +} + +fn into_impl(count: usize) -> TokenStream2 { + let into_impl_single = { + let name = field_name_for(1); + quote!( + for (voter_index, target_index) in self.#name { + assignments.push(_phragmen::Assignment { + who: voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, + distribution: vec![ + (target_at(target_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, Accuracy::one()) + ], + }) + } + ) + }; + + let into_impl_double = { + let name = field_name_for(2); + quote!( + for (voter_index, (t1_idx, p1), t2_idx) in self.#name { + if p1 >= Accuracy::one() { + return Err(_phragmen::Error::CompactStakeOverflow); + } + + // defensive only. Since Percent doesn't have `Sub`. + let p2 = _phragmen::sp_runtime::traits::Saturating::saturating_sub( + Accuracy::one(), + p1, + ); + + assignments.push( _phragmen::Assignment { + who: voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, + distribution: vec![ + (target_at(t1_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, p1), + (target_at(t2_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, p2), + ] + }); + } + ) + }; + + let into_impl_rest = (3..=count).map(|c| { + let name = field_name_for(c); + quote!( + for (voter_index, inners, t_last_idx) in self.#name { + let mut sum = Accuracy::zero(); + let mut inners_parsed = inners + .iter() + .map(|(ref t_idx, p)| { + sum = _phragmen::sp_runtime::traits::Saturating::saturating_add(sum, *p); + let target = target_at(*t_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?; + Ok((target, *p)) + }) + .collect::, _phragmen::Error>>()?; + + if sum >= Accuracy::one() { + return Err(_phragmen::Error::CompactStakeOverflow); + } + + // defensive only. Since Percent doesn't have `Sub`. + let p_last = _phragmen::sp_runtime::traits::Saturating::saturating_sub( + Accuracy::one(), + sum, + ); + + inners_parsed.push((target_at(t_last_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, p_last)); + + assignments.push(_phragmen::Assignment { + who: voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, + distribution: inners_parsed, + }); + } + ) + }).collect::(); + + quote!( + #into_impl_single + #into_impl_double + #into_impl_rest + ) +} + +pub(crate) fn assignment( + ident: syn::Ident, + voter_type: GenericArgument, + target_type: GenericArgument, + count: usize, +) -> TokenStream2 { + + let from_impl = from_impl(count); + let into_impl = into_impl(count); + + quote!( + impl< + #voter_type: _phragmen::codec::Codec + Default + Copy, + #target_type: _phragmen::codec::Codec + Default + Copy, + Accuracy: + _phragmen::codec::Codec + Default + Clone + _phragmen::sp_runtime::PerThing + + PartialOrd, + > + #ident<#voter_type, #target_type, Accuracy> + { + pub fn from_assignment( + assignments: Vec<_phragmen::Assignment>, + index_of_voter: FV, + index_of_target: FT, + ) -> Result + where + for<'r> FV: Fn(&'r A) -> Option<#voter_type>, + for<'r> FT: Fn(&'r A) -> Option<#target_type>, + A: _phragmen::IdentifierT, + { + let mut compact: #ident< + #voter_type, + #target_type, + Accuracy, + > = Default::default(); + + for _phragmen::Assignment { who, distribution } in assignments { + match distribution.len() { + 0 => continue, + #from_impl + _ => { + return Err(_phragmen::Error::CompactTargetOverflow); + } + } + }; + Ok(compact) + } + + pub fn into_assignment( + self, + voter_at: impl Fn(#voter_type) -> Option, + target_at: impl Fn(#target_type) -> Option, + ) -> Result>, _phragmen::Error> { + let mut assignments: Vec<_phragmen::Assignment> = Default::default(); + #into_impl + Ok(assignments) + } + } + ) +} diff --git a/primitives/phragmen/compact/src/lib.rs b/primitives/phragmen/compact/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..114aeaeb32ed92ae4badbb59d38dc77cbcf309e7 --- /dev/null +++ b/primitives/phragmen/compact/src/lib.rs @@ -0,0 +1,219 @@ +// Copyright 2020 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 . + +//! Proc macro for phragmen compact assignment. + +use proc_macro::TokenStream; +use proc_macro2::{TokenStream as TokenStream2, Span, Ident}; +use proc_macro_crate::crate_name; +use quote::quote; +use syn::{GenericArgument, Type, parse::{Parse, ParseStream, Result}}; + +mod assignment; +mod staked; + +// prefix used for struct fields in compact. +const PREFIX: &'static str = "votes"; + +/// Generates a struct to store the phragmen assignments in a compact way. The struct can only store +/// distributions up to the given input count. The given count must be greater than 2. +/// +/// ```ignore +/// // generate a struct with nominator and edge weight u128, with maximum supported +/// // edge per voter of 16. +/// generate_compact_solution_type(pub TestCompact, 16) +/// ``` +/// +/// This generates: +/// +/// ```ignore +/// pub struct TestCompact { +/// votes1: Vec<(V, T)>, +/// votes2: Vec<(V, (T, W), T)>, +/// votes3: Vec<(V, [(T, W); 2usize], T)>, +/// votes4: Vec<(V, [(T, W); 3usize], T)>, +/// votes5: Vec<(V, [(T, W); 4usize], T)>, +/// votes6: Vec<(V, [(T, W); 5usize], T)>, +/// votes7: Vec<(V, [(T, W); 6usize], T)>, +/// votes8: Vec<(V, [(T, W); 7usize], T)>, +/// votes9: Vec<(V, [(T, W); 8usize], T)>, +/// votes10: Vec<(V, [(T, W); 9usize], T)>, +/// votes11: Vec<(V, [(T, W); 10usize], T)>, +/// votes12: Vec<(V, [(T, W); 11usize], T)>, +/// votes13: Vec<(V, [(T, W); 12usize], T)>, +/// votes14: Vec<(V, [(T, W); 13usize], T)>, +/// votes15: Vec<(V, [(T, W); 14usize], T)>, +/// votes16: Vec<(V, [(T, W); 15usize], T)>, +/// } +/// ``` +/// +/// The generic arguments are: +/// - `V`: identifier/index for voter (nominator) types. +/// - `T` identifier/index for candidate (validator) types. +/// - `W` weight type. +/// +/// Some conversion implementations are provided by default if +/// - `W` is u128, or +/// - `W` is anything that implements `PerThing` (such as `Perbill`) +/// +/// The ideas behind the structure are as follows: +/// +/// - For single distribution, no weight is stored. The weight is known to be 100%. +/// - For all the rest, the weight if the last distribution is omitted. This value can be computed +/// from the rest. +/// +#[proc_macro] +pub fn generate_compact_solution_type(item: TokenStream) -> TokenStream { + let CompactSolutionDef { + vis, + ident, + count, + } = syn::parse_macro_input!(item as CompactSolutionDef); + + let voter_type = GenericArgument::Type(Type::Verbatim(quote!(V))); + let target_type = GenericArgument::Type(Type::Verbatim(quote!(T))); + let weight_type = GenericArgument::Type(Type::Verbatim(quote!(W))); + + let imports = imports().unwrap_or_else(|e| e.to_compile_error()); + + let compact_def = struct_def( + vis, + ident.clone(), + count, + voter_type.clone(), + target_type.clone(), + weight_type, + ).unwrap_or_else(|e| e.to_compile_error()); + + let assignment_impls = assignment::assignment( + ident.clone(), + voter_type.clone(), + target_type.clone(), + count, + ); + + let staked_impls = staked::staked( + ident, + voter_type, + target_type, + count, + ); + + quote!( + #imports + #compact_def + #assignment_impls + #staked_impls + ).into() +} + +fn struct_def( + vis: syn::Visibility, + ident: syn::Ident, + count: usize, + voter_type: GenericArgument, + target_type: GenericArgument, + weight_type: GenericArgument, +) -> Result { + if count <= 2 { + Err(syn::Error::new( + Span::call_site(), + "cannot build compact solution struct with capacity less than 2." + ))? + } + + let singles = { + let name = field_name_for(1); + quote!(#name: Vec<(#voter_type, #target_type)>,) + }; + + let doubles = { + let name = field_name_for(2); + quote!(#name: Vec<(#voter_type, (#target_type, #weight_type), #target_type)>,) + }; + + let rest = (3..=count).map(|c| { + let field_name = field_name_for(c); + let array_len = c - 1; + quote!( + #field_name: Vec<( + #voter_type, + [(#target_type, #weight_type); #array_len], + #target_type + )>, + ) + }).collect::(); + + Ok(quote! ( + /// A struct to encode a Phragmen assignment in a compact way. + #[derive( + Default, + PartialEq, + Eq, + Clone, + _phragmen::sp_runtime::RuntimeDebug, + _phragmen::codec::Encode, + _phragmen::codec::Decode, + )] + #vis struct #ident<#voter_type, #target_type, #weight_type> { + // _marker: sp_std::marker::PhantomData, + #singles + #doubles + #rest + } + + impl<#voter_type, #target_type, #weight_type> _phragmen::VotingLimit + for #ident<#voter_type, #target_type, #weight_type> + { + const LIMIT: usize = #count; + } + )) +} + +fn imports() -> Result { + let sp_phragmen_imports = match crate_name("sp-phragmen") { + Ok(sp_phragmen) => { + let ident = syn::Ident::new(&sp_phragmen, Span::call_site()); + quote!( extern crate #ident as _phragmen; ) + } + Err(e) => return Err(syn::Error::new(Span::call_site(), &e)), + }; + + Ok(quote!( + #sp_phragmen_imports + )) +} + +struct CompactSolutionDef { + vis: syn::Visibility, + ident: syn::Ident, + count: usize, +} + +impl Parse for CompactSolutionDef { + fn parse(input: ParseStream) -> syn::Result { + let vis: syn::Visibility = input.parse()?; + let ident: syn::Ident = input.parse()?; + let _ = ::parse(input)?; + let count_literal: syn::LitInt = input.parse()?; + let count = count_literal.base10_parse::()?; + Ok(Self { vis, ident, count } ) + } +} + +fn field_name_for(n: usize) -> Ident { + Ident::new(&format!("{}{}", PREFIX, n), Span::call_site()) +} diff --git a/primitives/phragmen/compact/src/staked.rs b/primitives/phragmen/compact/src/staked.rs new file mode 100644 index 0000000000000000000000000000000000000000..a7cf853f17086651ef4be37dcf2538cdce936dc6 --- /dev/null +++ b/primitives/phragmen/compact/src/staked.rs @@ -0,0 +1,208 @@ +// Copyright 2020 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 . + +//! Code generation for the staked assignment type. + +use crate::field_name_for; +use proc_macro2::{TokenStream as TokenStream2}; +use syn::{GenericArgument}; +use quote::quote; + +fn from_impl(count: usize) -> TokenStream2 { + let from_impl_single = { + let name = field_name_for(1); + quote!(1 => compact.#name.push( + ( + index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, + index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ) + ),) + }; + + let from_impl_double = { + let name = field_name_for(2); + quote!(2 => compact.#name.push( + ( + index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ( + index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + distribution[0].1, + ), + index_of_target(&distribution[1].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ) + ),) + }; + + let from_impl_rest = (3..=count).map(|c| { + let inner = (0..c-1).map(|i| + quote!((index_of_target(&distribution[#i].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, distribution[#i].1),) + ).collect::(); + + let field_name = field_name_for(c); + let last_index = c - 1; + let last = quote!(index_of_target(&distribution[#last_index].0).ok_or(_phragmen::Error::CompactInvalidIndex)?); + + quote!( + #c => compact.#field_name.push((index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, [#inner], #last)), + ) + }).collect::(); + + quote!( + #from_impl_single + #from_impl_double + #from_impl_rest + ) +} + +fn into_impl(count: usize) -> TokenStream2 { + let into_impl_single = { + let name = field_name_for(1); + quote!( + for (voter_index, target_index) in self.#name { + let who = voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?; + let all_stake = max_of(&who); + assignments.push(_phragmen::StakedAssignment { + who, + distribution: vec![(target_at(target_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, all_stake)], + }) + } + ) + }; + + let into_impl_double = { + let name = field_name_for(2); + quote!( + for (voter_index, (t1_idx, w1), t2_idx) in self.#name { + let who = voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?; + let all_stake = max_of(&who); + + if w1 >= all_stake { + return Err(_phragmen::Error::CompactStakeOverflow); + } + + // w2 is ensured to be positive. + let w2 = all_stake - w1; + assignments.push( _phragmen::StakedAssignment { + who, + distribution: vec![ + (target_at(t1_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, w1), + (target_at(t2_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, w2), + ] + }); + } + ) + }; + + let into_impl_rest = (3..=count).map(|c| { + let name = field_name_for(c); + quote!( + for (voter_index, inners, t_last_idx) in self.#name { + let who = voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?; + let mut sum = u128::min_value(); + let all_stake = max_of(&who); + + let mut inners_parsed = inners + .iter() + .map(|(ref t_idx, w)| { + sum = sum.saturating_add(*w); + let target = target_at(*t_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?; + Ok((target, *w)) + }).collect::, _phragmen::Error>>()?; + + if sum >= all_stake { + return Err(_phragmen::Error::CompactStakeOverflow); + } + // w_last is proved to be positive. + let w_last = all_stake - sum; + + inners_parsed.push((target_at(t_last_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, w_last)); + + assignments.push(_phragmen::StakedAssignment { + who, + distribution: inners_parsed, + }); + } + ) + }).collect::(); + + quote!( + #into_impl_single + #into_impl_double + #into_impl_rest + ) +} + +pub(crate) fn staked( + ident: syn::Ident, + voter_type: GenericArgument, + target_type: GenericArgument, + count: usize, +) -> TokenStream2 { + + let from_impl = from_impl(count); + let into_impl = into_impl(count); + quote!( + impl< + #voter_type: _phragmen::codec::Codec + Default + Copy, + #target_type: _phragmen::codec::Codec + Default + Copy, + > + #ident<#voter_type, #target_type, u128> + { + /// Generate self from a vector of `StakedAssignment`. + pub fn from_staked( + assignments: Vec<_phragmen::StakedAssignment>, + index_of_voter: FV, + index_of_target: FT, + ) -> Result + where + for<'r> FV: Fn(&'r A) -> Option<#voter_type>, + for<'r> FT: Fn(&'r A) -> Option<#target_type>, + A: _phragmen::IdentifierT + { + let mut compact: #ident<#voter_type, #target_type, u128> = Default::default(); + for _phragmen::StakedAssignment { who, distribution } in assignments { + match distribution.len() { + 0 => continue, + #from_impl + _ => { + return Err(_phragmen::Error::CompactTargetOverflow); + } + } + }; + Ok(compact) + } + + /// Convert self into `StakedAssignment`. The given function should return the total + /// weight of a voter. It is used to subtract the sum of all the encoded weights to + /// infer the last one. + pub fn into_staked( + self, + max_of: FM, + voter_at: impl Fn(#voter_type) -> Option, + target_at: impl Fn(#target_type) -> Option, + ) + -> Result>, _phragmen::Error> + where + for<'r> FM: Fn(&'r A) -> u128, + A: _phragmen::IdentifierT, + { + let mut assignments: Vec<_phragmen::StakedAssignment> = Default::default(); + #into_impl + Ok(assignments) + } + } + ) +} diff --git a/primitives/arithmetic/fuzzer/.gitignore b/primitives/phragmen/fuzzer/.gitignore similarity index 100% rename from primitives/arithmetic/fuzzer/.gitignore rename to primitives/phragmen/fuzzer/.gitignore diff --git a/primitives/phragmen/fuzzer/Cargo.lock b/primitives/phragmen/fuzzer/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..3ef2a2732424a3ddd412192dc01ac0647c36ffe1 --- /dev/null +++ b/primitives/phragmen/fuzzer/Cargo.lock @@ -0,0 +1,1602 @@ +# 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.0" +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-phragmen" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "serde", + "sp-core", + "sp-phragmen-compact", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-phragmen-compact" +version = "2.0.0-dev" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-phragmen-fuzzer" +version = "2.0.0" +dependencies = [ + "honggfuzz", + "rand 0.7.3", + "sp-phragmen", +] + +[[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/phragmen/fuzzer/Cargo.toml b/primitives/phragmen/fuzzer/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..90af69a707f75a84b417e2729abf6a245061bb41 --- /dev/null +++ b/primitives/phragmen/fuzzer/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sp-phragmen-fuzzer" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +sp-phragmen = { version = "2.0.0-alpha.3", path = ".." } +honggfuzz = "0.5" +rand = "0.7.3" + +[workspace] + +[[bin]] +name = "reduce" +path = "src/reduce.rs" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/phragmen/fuzzer/src/reduce.rs b/primitives/phragmen/fuzzer/src/reduce.rs new file mode 100644 index 0000000000000000000000000000000000000000..4bf08590a149feafae1fc9ab33372c24440377de --- /dev/null +++ b/primitives/phragmen/fuzzer/src/reduce.rs @@ -0,0 +1,145 @@ +// Copyright 2020 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 . + +//! # Running +//! +//! Run with `cargo hfuzz run reduce`. `honggfuzz`. +//! +//! # Debugging a panic +//! +//! Once a panic is found, it can be debugged with +//! `cargo hfuzz run-debug reduce hfuzz_workspace/reduce/*.fuzz`. + +use honggfuzz::fuzz; +use sp_phragmen::{StakedAssignment, ExtendedBalance, build_support_map, reduce}; +use rand::{self, Rng}; + +type Balance = u128; +type AccountId = u64; + +/// Or any other token type. +const KSM: Balance = 1_000_000_000_000; + +fn main() { + loop { + fuzz!(|_data: _| { + let (assignments, winners) = generate_random_phragmen_assignment( + rr(100, 1000), + rr(100, 2000), + 8, + 8, + ); + reduce_and_compare(&assignments, &winners); + }); + } +} + +fn generate_random_phragmen_assignment( + voter_count: usize, + target_count: usize, + avg_edge_per_voter: usize, + edge_per_voter_var: usize, +) -> (Vec>, Vec) { + // random in range of (a, b) + let rr_128 = |a: u128, b: u128| -> u128 { rand::thread_rng().gen_range(a, b) }; + + // prefix to distinguish the voter and target account ranges. + let target_prefix = 1_000_000; + // let target_prefix = 1000; + assert!(voter_count < target_prefix); + + let mut assignments = Vec::with_capacity(voter_count as usize); + let mut winners: Vec = Vec::new(); + + let all_targets = (target_prefix..(target_prefix + target_count)) + .map(|a| a as AccountId) + .collect::>(); + + (1..=voter_count).for_each(|acc| { + let mut targets_to_chose_from = all_targets.clone(); + let targets_to_chose = if edge_per_voter_var > 0 { rr( + avg_edge_per_voter - edge_per_voter_var, + avg_edge_per_voter + edge_per_voter_var, + ) } else { avg_edge_per_voter }; + + let distribution = (0..targets_to_chose).map(|_| { + let target = targets_to_chose_from.remove(rr(0, targets_to_chose_from.len())); + if winners.iter().find(|w| **w == target).is_none() { + winners.push(target.clone()); + } + (target, rr_128(1 * KSM, 100 * KSM)) + }).collect::>(); + + assignments.push(StakedAssignment { + who: (acc as AccountId), + distribution, + }); + }); + + (assignments, winners) +} + +fn assert_assignments_equal( + winners: &Vec, + ass1: &Vec>, + ass2: &Vec>, +) { + + let (support_1, _) = build_support_map::(winners, ass1); + let (support_2, _) = build_support_map::(winners, ass2); + + for (who, support) in support_1.iter() { + assert_eq!(support.total, support_2.get(who).unwrap().total); + } +} + +fn reduce_and_compare( + assignment: &Vec>, + winners: &Vec, +) { + let mut altered_assignment = assignment.clone(); + let n = assignment.len() as u32; + let m = winners.len() as u32; + + let edges_before = assignment_len(&assignment); + let num_changed = reduce(&mut altered_assignment); + let edges_after = edges_before - num_changed; + + assert!( + edges_after <= m + n, + "reduce bound not satisfied. n = {}, m = {}, edges after reduce = {} (removed {})", + n, + m, + edges_after, + num_changed, + ); + + assert_assignments_equal( + winners, + &assignment, + &altered_assignment, + ); +} + +fn assignment_len(assignments: &[StakedAssignment]) -> u32 { + let mut counter = 0; + assignments.iter().for_each(|x| x.distribution.iter().for_each(|_| counter += 1)); + counter +} + +fn rr(a: usize, b: usize) -> usize { + rand::thread_rng().gen_range(a, b) +} diff --git a/primitives/phragmen/src/helpers.rs b/primitives/phragmen/src/helpers.rs new file mode 100644 index 0000000000000000000000000000000000000000..27f51b4a05fe2c2c22a02f8442efdd9d9f86124d --- /dev/null +++ b/primitives/phragmen/src/helpers.rs @@ -0,0 +1,94 @@ +// Copyright 2020 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 methods for phragmen. + +use crate::{Assignment, ExtendedBalance, IdentifierT, StakedAssignment}; +use sp_runtime::PerThing; +use sp_std::prelude::*; + +/// Converts a vector of ratio assignments into ones with absolute budget value. +pub fn assignment_ratio_to_staked( + ratio: Vec>, + stake_of: FS, +) -> Vec> +where + for<'r> FS: Fn(&'r A) -> ExtendedBalance, + T: sp_std::ops::Mul, + ExtendedBalance: From<::Inner>, +{ + ratio + .into_iter() + .map(|a| { + let stake = stake_of(&a.who); + a.into_staked(stake, true) + }) + .collect() +} + +/// Converts a vector of staked assignments into ones with ratio values. +pub fn assignment_staked_to_ratio( + ratio: Vec>, +) -> Vec> +where + ExtendedBalance: From<::Inner>, +{ + ratio.into_iter().map(|a| a.into_assignment(true)).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ExtendedBalance; + use sp_runtime::Perbill; + + #[test] + fn into_staked_works() { + let ratio = vec![ + Assignment { + who: 1u32, + distribution: vec![ + (10u32, Perbill::from_fraction(0.5)), + (20, Perbill::from_fraction(0.5)), + ], + }, + Assignment { + who: 2u32, + distribution: vec![ + (10, Perbill::from_fraction(0.33)), + (20, Perbill::from_fraction(0.67)), + ], + }, + ]; + + let stake_of = |_: &u32| -> ExtendedBalance { 100u128 }; + let staked = assignment_ratio_to_staked(ratio, stake_of); + + assert_eq!( + staked, + vec![ + StakedAssignment { + who: 1u32, + distribution: vec![(10u32, 50), (20, 50),] + }, + StakedAssignment { + who: 2u32, + distribution: vec![(10u32, 33), (20, 67),] + } + ] + ); + } +} diff --git a/primitives/phragmen/src/lib.rs b/primitives/phragmen/src/lib.rs index 23acec19da9f83e884ca65e6058760a88dd4b4a8..c0d94a71e1fc209f4db7085bd8fcba0262d66ecd 100644 --- a/primitives/phragmen/src/lib.rs +++ b/primitives/phragmen/src/lib.rs @@ -33,19 +33,60 @@ #![cfg_attr(not(feature = "std"), no_std)] -use sp_std::{prelude::*, collections::btree_map::BTreeMap, convert::TryFrom}; -use sp_runtime::{ - PerThing, Rational128, RuntimeDebug, - helpers_128bit::multiply_by_rational, -}; -use sp_runtime::traits::{ - Zero, Convert, Member, AtLeast32Bit, SaturatedConversion, Bounded, Saturating, -}; +use sp_std::{prelude::*, collections::btree_map::BTreeMap, fmt::Debug, cmp::Ordering, convert::TryFrom}; +use sp_runtime::{helpers_128bit::multiply_by_rational, PerThing, Rational128, RuntimeDebug, SaturatedConversion}; +use sp_runtime::traits::{Zero, Convert, Member, AtLeast32Bit, Saturating, Bounded}; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize}; +#[cfg(feature = "std")] +use codec::{Encode, Decode}; + +mod node; +mod reduce; +mod helpers; + +// re-export reduce stuff. +pub use reduce::reduce; + +// re-export the helpers. +pub use helpers::*; + +// re-export the compact macro, with the dependencies of the macro. +#[doc(hidden)] +pub use codec; +#[doc(hidden)] +pub use sp_runtime; + +// re-export the compact solution type. +pub use sp_phragmen_compact::generate_compact_solution_type; + +/// A trait to limit the number of votes per voter. The generated compact type will implement this. +pub trait VotingLimit { + const LIMIT: usize; +} + +/// 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 {} + +/// The errors that might occur in the this crate and compact. +#[derive(Debug, Eq, PartialEq)] +pub enum Error { + /// While going from compact to staked, the stake of all the edges has gone above the + /// total and the last stake cannot be assigned. + CompactStakeOverflow, + /// The compact type has a voter who's number of targets is out of bound. + CompactTargetOverflow, + /// One of the index functions returned none. + CompactInvalidIndex, +} /// A type in which performing operations on balances and stakes of candidates and voters are safe. /// @@ -55,6 +96,9 @@ mod tests; /// Balance types converted to `ExtendedBalance` are referred to as `Votes`. pub type ExtendedBalance = u128; +/// The score of an assignment. This can be computed from the support map via [`evaluate_support`]. +pub type PhragmenScore = [ExtendedBalance; 3]; + /// The denominator used for loads. Since votes are collected as u64, the smallest ratio that we /// might collect is `1/approval_stake` where approval stake is the sum of votes. Hence, some number /// bigger than u64::max_value() is needed. For maximum accuracy we simply use u128; @@ -62,11 +106,11 @@ const DEN: u128 = u128::max_value(); /// A candidate entity for phragmen election. #[derive(Clone, Default, RuntimeDebug)] -pub struct Candidate { +struct Candidate { /// Identifier. - pub who: AccountId, + who: AccountId, /// Intermediary value used to sort candidates. - pub score: Rational128, + score: Rational128, /// Sum of the stake of this candidate based on received votes. approval_stake: ExtendedBalance, /// Flag for being elected. @@ -75,7 +119,7 @@ pub struct Candidate { /// A voter entity. #[derive(Clone, Default, RuntimeDebug)] -pub struct Voter { +struct Voter { /// Identifier. who: AccountId, /// List of candidates proposed by this voter. @@ -88,7 +132,7 @@ pub struct Voter { /// A candidate being backed by a voter. #[derive(Clone, Default, RuntimeDebug)] -pub struct Edge { +struct Edge { /// Identifier. who: AccountId, /// Load of this vote. @@ -97,12 +141,6 @@ pub struct Edge { candidate_index: usize, } -/// Particular `AccountId` was backed by `T`-ratio of a nominator's stake. -pub type PhragmenAssignment = (AccountId, T); - -/// Particular `AccountId` was backed by `ExtendedBalance` of a nominator's stake. -pub type PhragmenStakedAssignment = (AccountId, ExtendedBalance); - /// Final result of the phragmen election. #[derive(RuntimeDebug)] pub struct PhragmenResult { @@ -111,7 +149,142 @@ pub struct PhragmenResult { pub winners: Vec<(AccountId, ExtendedBalance)>, /// Individual assignments. for each tuple, the first elements is a voter and the second /// is the list of candidates that it supports. - pub assignments: Vec<(AccountId, Vec>)> + pub assignments: Vec>, +} + +/// A voter's stake assignment among a set of targets, represented as ratios. +#[derive(RuntimeDebug, Clone, Default)] +#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] +pub struct Assignment { + /// Voter's identifier + pub who: AccountId, + /// The distribution of the voter's stake. + pub distribution: Vec<(AccountId, T)>, +} + +impl Assignment +where + ExtendedBalance: From<::Inner>, +{ + /// Convert from a ratio assignment into one with absolute values aka. [`StakedAssignment`]. + /// + /// It needs `stake` which is the total budget of the voter. If `fill` is set to true, + /// it _tries_ to ensure that all the potential rounding errors are compensated and the + /// distribution's sum is exactly equal to the total budget, by adding or subtracting the + /// remainder from the last distribution. + /// + /// If an edge ratio is [`Bounded::max_value()`], it is dropped. This edge can never mean + /// anything useful. + pub fn into_staked(self, stake: ExtendedBalance, fill: bool) -> StakedAssignment + where + T: sp_std::ops::Mul, + { + let mut sum: ExtendedBalance = Bounded::min_value(); + let mut distribution = self + .distribution + .into_iter() + .filter_map(|(target, p)| { + // if this ratio is zero, then skip it. + if p == Bounded::min_value() { + None + } else { + // NOTE: this mul impl will always round to the nearest number, so we might both + // overflow and underflow. + let distribution_stake = p * stake; + // defensive only. We assume that balance cannot exceed extended balance. + sum = sum.saturating_add(distribution_stake); + Some((target, distribution_stake)) + } + }) + .collect::>(); + + if fill { + // NOTE: we can do this better. + // https://revs.runtime-revolution.com/getting-100-with-rounded-percentages-273ffa70252b + if let Some(leftover) = stake.checked_sub(sum) { + if let Some(last) = distribution.last_mut() { + last.1 = last.1.saturating_add(leftover); + } + } else if let Some(excess) = sum.checked_sub(stake) { + if let Some(last) = distribution.last_mut() { + last.1 = last.1.saturating_sub(excess); + } + } + } + + StakedAssignment { + who: self.who, + distribution, + } + } +} + +/// A voter's stake assignment among a set of targets, represented as absolute values in the scale +/// of [`ExtendedBalance`]. +#[derive(RuntimeDebug, Clone, Default)] +#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] +pub struct StakedAssignment { + /// Voter's identifier + pub who: AccountId, + /// The distribution of the voter's stake. + pub distribution: Vec<(AccountId, ExtendedBalance)>, +} + +impl StakedAssignment { + /// Converts self into the normal [`Assignment`] type. + /// + /// If `fill` is set to true, it _tries_ to ensure that all the potential rounding errors are + /// compensated and the distribution's sum is exactly equal to 100%, by adding or subtracting + /// the remainder from the last distribution. + /// + /// NOTE: it is quite critical that this attempt always works. The data type returned here will + /// potentially get used to create a compact type; a compact type requires sum of ratios to be + /// less than 100% upon un-compacting. + /// + /// If an edge stake is so small that it cannot be represented in `T`, it is ignored. This edge + /// can never be re-created and does not mean anything useful anymore. + pub fn into_assignment(self, fill: bool) -> Assignment + where + ExtendedBalance: From<::Inner>, + { + let accuracy: u128 = T::ACCURACY.saturated_into(); + let mut sum: u128 = Zero::zero(); + let stake = self.distribution.iter().map(|x| x.1).sum(); + let mut distribution = self + .distribution + .into_iter() + .filter_map(|(target, w)| { + let per_thing = T::from_rational_approximation(w, stake); + if per_thing == Bounded::min_value() { + None + } else { + sum += per_thing.clone().deconstruct().saturated_into(); + Some((target, per_thing)) + } + }) + .collect::>(); + + if fill { + if let Some(leftover) = accuracy.checked_sub(sum) { + if let Some(last) = distribution.last_mut() { + last.1 = last.1.saturating_add( + T::from_parts(leftover.saturated_into()) + ); + } + } else if let Some(excess) = sum.checked_sub(accuracy) { + if let Some(last) = distribution.last_mut() { + last.1 = last.1.saturating_sub( + T::from_parts(excess.saturated_into()) + ); + } + } + } + + Assignment { + who: self.who, + distribution, + } + } } /// A structure to demonstrate the phragmen result from the perspective of the candidate, i.e. how @@ -122,12 +295,12 @@ pub struct PhragmenResult { /// 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)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Eq, PartialEq))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Eq, PartialEq))] pub struct Support { /// Total support. pub total: ExtendedBalance, /// Support from voters. - pub voters: Vec>, + pub voters: Vec<(AccountId, ExtendedBalance)>, } /// A linkage from a candidate and its [`Support`]. @@ -149,16 +322,14 @@ pub type SupportMap = BTreeMap>; /// responsibility of the caller to make sure only those candidates who have a sensible economic /// value are passed in. From the perspective of this function, a candidate can easily be among the /// winner with no backing stake. -pub fn elect( +pub fn elect( candidate_count: usize, minimum_candidate_count: usize, initial_candidates: Vec, - initial_voters: Vec<(AccountId, Vec)>, - stake_of: FS, + initial_voters: Vec<(AccountId, Balance, Vec)>, ) -> Option> where AccountId: Default + Ord + Member, Balance: Default + Copy + AtLeast32Bit, - for<'r> FS: Fn(&'r AccountId) -> Balance, C: Convert + Convert, R: PerThing, { @@ -166,7 +337,7 @@ pub fn elect( // return structures let mut elected_candidates: Vec<(AccountId, ExtendedBalance)>; - let mut assigned: Vec<(AccountId, Vec>)>; + let mut assigned: Vec>; // used to cache and access candidates index. let mut c_idx_cache = BTreeMap::::new(); @@ -191,8 +362,7 @@ pub fn elect( // collect voters. use `c_idx_cache` for fast access and aggregate `approval_stake` of // candidates. - voters.extend(initial_voters.into_iter().map(|(who, votes)| { - let voter_stake = stake_of(&who); + voters.extend(initial_voters.into_iter().map(|(who, voter_stake, votes)| { let mut edges: Vec> = Vec::with_capacity(votes.len()); for v in votes { if let Some(idx) = c_idx_cache.get(&v) { @@ -274,7 +444,10 @@ pub fn elect( // update backing stake of candidates and voters for n in &mut voters { - let mut assignment = (n.who.clone(), vec![]); + let mut assignment = Assignment { + who: n.who.clone(), + ..Default::default() + }; for e in &mut n.edges { if elected_candidates.iter().position(|(ref c, _)| *c == e.who).is_some() { let per_bill_parts: R::Inner = @@ -301,44 +474,45 @@ pub fn elect( // R::Inner. .unwrap_or(Bounded::max_value()) } else { - // defensive only. Both edge and nominator loads are built from + // defensive only. Both edge and voter loads are built from // scores, hence MUST have the same denominator. Zero::zero() } } }; let per_thing = R::from_parts(per_bill_parts); - assignment.1.push((e.who.clone(), per_thing)); + assignment.distribution.push((e.who.clone(), per_thing)); } } - if assignment.1.len() > 0 { - // To ensure an assertion indicating: no stake from the nominator going to waste, + let len = assignment.distribution.len(); + if len > 0 { + // To ensure an assertion indicating: no stake from the voter going to waste, // we add a minimal post-processing to equally assign all of the leftover stake ratios. - let vote_count: R::Inner = assignment.1.len().saturated_into(); - let len = assignment.1.len(); - let mut sum: R::Inner = Zero::zero(); - assignment.1.iter().for_each(|a| sum = sum.saturating_add(a.1.deconstruct())); + let vote_count: R::Inner = len.saturated_into(); let accuracy = R::ACCURACY; + let mut sum: R::Inner = Zero::zero(); + assignment.distribution.iter().for_each(|a| sum = sum.saturating_add(a.1.deconstruct())); + let diff = accuracy.saturating_sub(sum); let diff_per_vote = (diff / vote_count).min(accuracy); if !diff_per_vote.is_zero() { for i in 0..len { - let current_ratio = assignment.1[i % len].1; + let current_ratio = assignment.distribution[i % len].1; let next_ratio = current_ratio .saturating_add(R::from_parts(diff_per_vote)); - assignment.1[i % len].1 = next_ratio; + assignment.distribution[i % len].1 = next_ratio; } } - // `remainder` is set to be less than maximum votes of a nominator (currently 16). + // `remainder` is set to be less than maximum votes of a voter (currently 16). // safe to cast it to usize. let remainder = diff - diff_per_vote * vote_count; for i in 0..remainder.saturated_into::() { - let current_ratio = assignment.1[i % len].1; + let current_ratio = assignment.distribution[i % len].1; let next_ratio = current_ratio.saturating_add(R::from_parts(1u8.into())); - assignment.1[i % len].1 = next_ratio; + assignment.distribution[i % len].1 = next_ratio; } assigned.push(assignment); } @@ -350,39 +524,109 @@ pub fn elect( }) } -/// Build the support map from the given phragmen result. -pub fn build_support_map( - elected_stashes: &Vec, - assignments: &Vec<(AccountId, Vec>)>, - stake_of: FS, -) -> SupportMap where +/// Build the support map from the given phragmen result. It maps a flat structure like +/// +/// ```nocompile +/// assignments: vec![ +/// voter1, vec![(candidate1, w11), (candidate2, w12)], +/// voter2, vec![(candidate1, w21), (candidate2, w22)] +/// ] +/// ``` +/// +/// into a mapping of candidates and their respective support: +/// +/// ```nocompile +/// SupportMap { +/// candidate1: Support { +/// own:0, +/// total: w11 + w21, +/// others: vec![(candidate1, w11), (candidate2, w21)] +/// }, +/// candidate2: Support { +/// own:0, +/// total: w12 + w22, +/// others: vec![(candidate1, w12), (candidate2, w22)] +/// }, +/// } +/// ``` +/// +/// The second returned flag indicates the number of edges who didn't corresponded to an actual +/// winner from the given winner set. A value in this place larger than 0 indicates a potentially +/// faulty assignment. +/// +/// `O(E)` where `E` is the total number of edges. +pub fn build_support_map( + winners: &[AccountId], + assignments: &[StakedAssignment], +) -> (SupportMap, u32) where AccountId: Default + Ord + Member, - Balance: Default + Copy + AtLeast32Bit, - C: Convert + Convert, - for<'r> FS: Fn(&'r AccountId) -> Balance, - R: PerThing + sp_std::ops::Mul, { - let to_votes = |b: Balance| >::convert(b) as ExtendedBalance; + let mut errors = 0; // Initialize the support of each candidate. let mut supports = >::new(); - elected_stashes + winners .iter() .for_each(|e| { supports.insert(e.clone(), Default::default()); }); // build support struct. - for (n, assignment) in assignments.iter() { - for (c, per_thing) in assignment.iter() { - let nominator_stake = to_votes(stake_of(n)); - // AUDIT: it is crucially important for the `Mul` implementation of all - // per-things to be sound. - let other_stake = *per_thing * nominator_stake; + for StakedAssignment { who, distribution } in assignments.iter() { + for (c, weight_extended) in distribution.iter() { if let Some(support) = supports.get_mut(c) { - support.voters.push((n.clone(), other_stake)); - support.total = support.total.saturating_add(other_stake); + support.total = support.total.saturating_add(*weight_extended); + support.voters.push((who.clone(), *weight_extended)); + } else { + errors = errors.saturating_add(1); } } } - supports + (supports, errors) +} + +/// Evaluate a phragmen result, given the support map. The returned tuple contains: +/// +/// - Minimum support. This value must be **maximized**. +/// - Sum of all supports. This value must be **maximized**. +/// - Sum of all supports squared. This value must be **minimized**. +/// +/// `O(E)` where `E` is the total number of edges. +pub fn evaluate_support( + support: &SupportMap, +) -> PhragmenScore { + let mut min_support = ExtendedBalance::max_value(); + let mut sum: ExtendedBalance = Zero::zero(); + // NOTE: this will probably saturate but using big num makes it even slower. We'll have to see. + // This must run on chain.. + let mut sum_squared: ExtendedBalance = Zero::zero(); + for (_, support) in support.iter() { + sum += 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; + } + } + [min_support, sum, sum_squared] +} + +/// Compares two sets of phragmen scores based on desirability and returns true if `that` is +/// better than `this`. +/// +/// Evaluation is done in a lexicographic manner. +/// +/// Note that the third component should be minimized. +pub fn is_score_better(this: PhragmenScore, that: PhragmenScore) -> bool { + match that + .iter() + .enumerate() + .map(|(i, e)| e.cmp(&this[i])) + .collect::>() + .as_slice() + { + [Ordering::Greater, _, _] => true, + [Ordering::Equal, Ordering::Greater, _] => true, + [Ordering::Equal, Ordering::Equal, Ordering::Less] => true, + _ => false, + } } /// Performs equalize post-processing to the output of the election algorithm. This happens in @@ -391,13 +635,13 @@ pub fn build_support_map( /// /// No value is returned from the function and the `supports` parameter is updated. /// -/// * `assignments`: exactly the same is the output of phragmen. -/// * `supports`: mutable reference to s `SupportMap`. This parameter is updated. -/// * `tolerance`: maximum difference that can occur before an early quite happens. -/// * `iterations`: maximum number of iterations that will be processed. -/// * `stake_of`: something that can return the stake stake of a particular candidate or voter. +/// - `assignments`: exactly the same is the output of phragmen. +/// - `supports`: mutable reference to s `SupportMap`. This parameter is updated. +/// - `tolerance`: maximum difference that can occur before an early quite happens. +/// - `iterations`: maximum number of iterations that will be processed. +/// - `stake_of`: something that can return the stake stake of a particular candidate or voter. pub fn equalize( - mut assignments: Vec<(AccountId, Vec>)>, + mut assignments: Vec>, supports: &mut SupportMap, tolerance: ExtendedBalance, iterations: usize, @@ -411,13 +655,13 @@ pub fn equalize( for _i in 0..iterations { let mut max_diff = 0; - for (voter, assignment) in assignments.iter_mut() { - let voter_budget = stake_of(&voter); + for StakedAssignment { who, distribution } in assignments.iter_mut() { + let voter_budget = stake_of(&who); let diff = do_equalize::<_, _, C>( - voter, + who, voter_budget, - assignment, + distribution, supports, tolerance, ); @@ -435,7 +679,7 @@ pub fn equalize( fn do_equalize( voter: &AccountId, budget_balance: Balance, - elected_edges: &mut Vec>, + elected_edges: &mut Vec<(AccountId, ExtendedBalance)>, support_map: &mut SupportMap, tolerance: ExtendedBalance ) -> ExtendedBalance where diff --git a/primitives/phragmen/src/mock.rs b/primitives/phragmen/src/mock.rs index b3110a5dba713d232b881f73f35e96d7493b3fe9..31ce3d38c3500ef930d3676a141beabb8694b275 100644 --- a/primitives/phragmen/src/mock.rs +++ b/primitives/phragmen/src/mock.rs @@ -18,10 +18,10 @@ #![cfg(test)] -use crate::{elect, PhragmenResult, PhragmenAssignment}; +use crate::{elect, PhragmenResult, Assignment}; use sp_runtime::{ - assert_eq_error_rate, Perbill, PerThing, - traits::{Convert, Member, SaturatedConversion} + assert_eq_error_rate, PerThing, + traits::{Convert, Member, SaturatedConversion, Zero, One} }; use sp_std::collections::btree_map::BTreeMap; @@ -320,27 +320,27 @@ pub(crate) fn create_stake_of(stakes: &[(AccountId, Balance)]) } -pub fn check_assignments(assignments: Vec<(AccountId, Vec>)>) { - for (_, a) in assignments { - let sum: u32 = a.iter().map(|(_, p)| p.deconstruct()).sum(); - assert_eq_error_rate!(sum, Perbill::ACCURACY, 5); +pub fn check_assignments_sum(assignments: Vec>) { + for Assignment { distribution, .. } in assignments { + let mut sum: u128 = Zero::zero(); + distribution.iter().for_each(|(_, p)| sum += p.deconstruct().saturated_into()); + assert_eq_error_rate!(sum, T::ACCURACY.saturated_into(), 1); } } -pub(crate) fn run_and_compare( +pub(crate) fn run_and_compare( candidates: Vec, voters: Vec<(AccountId, Vec)>, - stake_of: Box Balance>, + stake_of: &Box Balance>, to_elect: usize, min_to_elect: usize, ) { // run fixed point code. - let PhragmenResult { winners, assignments } = elect::<_, _, _, TestCurrencyToVote, Perbill>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>( to_elect, min_to_elect, candidates.clone(), - voters.clone(), - &stake_of, + voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ).unwrap(); // run float poc code. @@ -354,14 +354,14 @@ pub(crate) fn run_and_compare( assert_eq!(winners, truth_value.winners); - for (nominator, assigned) in assignments.clone() { - if let Some(float_assignments) = truth_value.assignments.iter().find(|x| x.0 == nominator) { - for (candidate, per_thingy) in assigned { + for Assignment { who, distribution } in assignments.clone() { + if let Some(float_assignments) = truth_value.assignments.iter().find(|x| x.0 == who) { + for (candidate, per_thingy) in distribution { if let Some(float_assignment) = float_assignments.1.iter().find(|x| x.0 == candidate ) { assert_eq_error_rate!( - Perbill::from_fraction(float_assignment.1).deconstruct(), + Output::from_fraction(float_assignment.1).deconstruct(), per_thingy.deconstruct(), - 1, + Output::Inner::one(), ); } else { panic!("candidate mismatch. This should never happen.") @@ -372,7 +372,7 @@ pub(crate) fn run_and_compare( } } - check_assignments(assignments); + check_assignments_sum(assignments); } pub(crate) fn build_support_map_float( diff --git a/primitives/phragmen/src/node.rs b/primitives/phragmen/src/node.rs new file mode 100644 index 0000000000000000000000000000000000000000..92ef325a348724762f3c92f29849963c1cfe4edb --- /dev/null +++ b/primitives/phragmen/src/node.rs @@ -0,0 +1,287 @@ +// Copyright 2020 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 . + +//! (very) Basic implementation of a graph node used in the reduce algorithm. + +use sp_runtime::RuntimeDebug; +use sp_std::{cell::RefCell, fmt, prelude::*, rc::Rc}; + +/// The role that a node can accept. +#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, RuntimeDebug)] +pub(crate) enum NodeRole { + /// A voter. This is synonym to a nominator in a staking context. + Voter, + /// A target. This is synonym to a candidate/validator in a staking context. + Target, +} + +pub(crate) type RefCellOf = Rc>; +pub(crate) type NodeRef = RefCellOf>; + +/// Identifier of a node. This is particularly handy to have a proper `PartialEq` implementation. +/// Otherwise, self votes wouldn't have been indistinguishable. +#[derive(PartialOrd, Ord, Clone, PartialEq, Eq)] +pub(crate) struct NodeId { + /// An account-like identifier representing the node. + pub who: A, + /// The role of the node. + pub role: NodeRole, +} + +impl NodeId { + /// Create a new [`NodeId`]. + pub fn from(who: A, role: NodeRole) -> Self { + Self { who, role } + } +} + +#[cfg(feature = "std")] +impl sp_std::fmt::Debug for NodeId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> sp_std::fmt::Result { + write!( + f, + "Node({:?}, {:?})", + self.who, + if self.role == NodeRole::Voter { + "V" + } else { + "T" + } + ) + } +} + +/// A one-way graph note. This can only store a pointer to its parent. +#[derive(Clone)] +pub(crate) struct Node { + /// The identifier of the note. + pub(crate) id: NodeId, + /// The parent pointer. + pub(crate) parent: Option>, +} + +impl PartialEq for Node { + fn eq(&self, other: &Node) -> bool { + self.id == other.id + } +} + +impl Eq for Node {} + +#[cfg(feature = "std")] +impl fmt::Debug for Node { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "({:?} --> {:?})", + self.id, + self.parent.as_ref().map(|p| p.borrow().id.clone()) + ) + } +} + +impl Node { + /// Create a new [`Node`] + pub fn new(id: NodeId) -> Node { + Self { id, parent: None } + } + + /// Returns true if `other` is the parent of `who`. + pub fn is_parent_of(who: &NodeRef, other: &NodeRef) -> bool { + if who.borrow().parent.is_none() { + return false; + } + who.borrow().parent.as_ref() == Some(other) + } + + /// Removes the parent of `who`. + pub fn remove_parent(who: &NodeRef) { + who.borrow_mut().parent = None; + } + + /// Sets `who`'s parent to be `parent`. + pub fn set_parent_of(who: &NodeRef, parent: &NodeRef) { + who.borrow_mut().parent = Some(parent.clone()); + } + + /// Finds the root of `start`. It return a tuple of `(root, root_vec)` where `root_vec` is the + /// vector of Nodes leading to the root. Hence the first element is the start itself and the + /// last one is the root. As convenient, the root itself is also returned as the first element + /// of the tuple. + /// + /// This function detects cycles and breaks as soon a duplicate node is visited, returning the + /// cycle up to but not including the duplicate node. + /// + /// If you are certain that no cycles exist, you can use [`root_unchecked`]. + pub fn root(start: &NodeRef) -> (NodeRef, Vec>) { + let mut parent_path: Vec> = Vec::new(); + let mut visited: Vec> = Vec::new(); + + parent_path.push(start.clone()); + visited.push(start.clone()); + let mut current = start.clone(); + + while let Some(ref next_parent) = current.clone().borrow().parent { + if visited.contains(next_parent) { + break; + } + parent_path.push(next_parent.clone()); + current = next_parent.clone(); + visited.push(current.clone()); + } + + (current, parent_path) + } + + /// Consumes self and wraps it in a `Rc>`. This type can be used as the pointer type + /// to a parent node. + pub fn into_ref(self) -> NodeRef { + Rc::from(RefCell::from(self)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn id(i: u32) -> NodeId { + NodeId::from(i, NodeRole::Target) + } + + #[test] + fn basic_create_works() { + let node = Node::new(id(10)); + assert_eq!( + node, + Node { + id: NodeId { + who: 10, + role: NodeRole::Target + }, + parent: None + } + ); + } + + #[test] + fn set_parent_works() { + let a = Node::new(id(10)).into_ref(); + let b = Node::new(id(20)).into_ref(); + + assert_eq!(a.borrow().parent, None); + Node::set_parent_of(&a, &b); + assert_eq!(*a.borrow().parent.as_ref().unwrap(), b); + } + + #[test] + fn get_root_singular() { + let a = Node::new(id(1)).into_ref(); + assert_eq!(Node::root(&a), (a.clone(), vec![a.clone()])); + } + + #[test] + fn get_root_works() { + // D <-- A <-- B <-- C + // \ + // <-- E + let a = Node::new(id(1)).into_ref(); + let b = Node::new(id(2)).into_ref(); + let c = Node::new(id(3)).into_ref(); + let d = Node::new(id(4)).into_ref(); + let e = Node::new(id(5)).into_ref(); + let f = Node::new(id(6)).into_ref(); + + Node::set_parent_of(&c, &b); + Node::set_parent_of(&b, &a); + Node::set_parent_of(&e, &a); + Node::set_parent_of(&a, &d); + + assert_eq!( + Node::root(&e), + (d.clone(), vec![e.clone(), a.clone(), d.clone()]), + ); + + assert_eq!(Node::root(&a), (d.clone(), vec![a.clone(), d.clone()]),); + + assert_eq!( + Node::root(&c), + (d.clone(), vec![c.clone(), b.clone(), a.clone(), d.clone()]), + ); + + // D A <-- B <-- C + // F <-- / \ + // <-- E + Node::set_parent_of(&a, &f); + + assert_eq!(Node::root(&a), (f.clone(), vec![a.clone(), f.clone()]),); + + assert_eq!( + Node::root(&c), + (f.clone(), vec![c.clone(), b.clone(), a.clone(), f.clone()]), + ); + } + + #[test] + fn get_root_on_cycle() { + // A ---> B + // | | + // <---- C + let a = Node::new(id(1)).into_ref(); + let b = Node::new(id(2)).into_ref(); + let c = Node::new(id(3)).into_ref(); + + Node::set_parent_of(&a, &b); + Node::set_parent_of(&b, &c); + Node::set_parent_of(&c, &a); + + let (root, path) = Node::root(&a); + assert_eq!(root, c); + assert_eq!(path.clone(), vec![a.clone(), b.clone(), c.clone()]); + } + + #[test] + fn get_root_on_cycle_2() { + // A ---> B + // | | | + // - C + let a = Node::new(id(1)).into_ref(); + let b = Node::new(id(2)).into_ref(); + let c = Node::new(id(3)).into_ref(); + + Node::set_parent_of(&a, &b); + Node::set_parent_of(&b, &c); + Node::set_parent_of(&c, &b); + + let (root, path) = Node::root(&a); + assert_eq!(root, c); + assert_eq!(path.clone(), vec![a.clone(), b.clone(), c.clone()]); + } + + #[test] + fn node_cmp_stack_overflows_on_non_unique_elements() { + // To make sure we don't stack overflow on duplicate who. This needs manual impl of + // PartialEq. + let a = Node::new(id(1)).into_ref(); + let b = Node::new(id(2)).into_ref(); + let c = Node::new(id(3)).into_ref(); + + Node::set_parent_of(&a, &b); + Node::set_parent_of(&b, &c); + Node::set_parent_of(&c, &a); + + Node::root(&a); + } +} diff --git a/primitives/phragmen/src/reduce.rs b/primitives/phragmen/src/reduce.rs new file mode 100644 index 0000000000000000000000000000000000000000..54a71a7ff29aabdf80b35565f4f09c34fec5b808 --- /dev/null +++ b/primitives/phragmen/src/reduce.rs @@ -0,0 +1,1076 @@ +// Copyright 2020 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 . + +//! Rust implementation of the Phragmén reduce algorithm. This can be used by any off chain +//! application to reduce cycles from the edge assignment, which will result in smaller size. +//! +//! ### Notions +//! - `m`: size of the committee to elect. +//! - `k`: maximum allowed votes (16 as of this writing). +//! - `nv ∈ E` means that nominator `n ∈ N` supports the election of candidate `v ∈ V`. +//! - A valid solution consists of a tuple `(S, W)` , where `S ⊆ V` is a committee of m validators, +//! and `W : E → R ≥ 0` is an edge weight vector which describes how the budget of each nominator +//! n is fractionally assigned to n 's elected neighbors. +//! - `E_w := { e ∈ E : w_e > 0 }`. +//! +//! ### Algorithm overview +//! +//! > We consider the input edge weight vector `w` as a directed flow over `E_w` , where the flow in +//! > each edge is directed from the nominator to the validator. We build `w′` from `w` by removing +//! > **circulations** to cancel out the flow over as many edges as possible, while preserving flow +//! > demands over all vertices and without reverting the flow direction over any edge. As long as +//! > there is a cycle, we can remove an additional circulation and eliminate at least one new edge +//! > from `E_w′` . This shows that the algorithm always progresses and will eventually finish with +//! > an acyclic edge support. We keep a data structure that represents a forest of rooted trees, +//! > which is initialized as a collection of singletons – one per vertex – and to which edges in +//! > `E_w` are added one by one, causing the trees to merge. Whenever a new edge creates a cycle, +//! > we detect it and destroy it by removing a circulation. We also run a pre-computation which is +//! > designed to detect and remove cycles of length four exclusively. This pre-computation is +//! > optional, and if we skip it then the running time becomes `O (|E_w| ⋅ m), so the +//! > pre-computation makes sense only if `m >> k` and `|E_w| >> m^2`. +//! +//! ### Resources: +//! +//! 1. https://hackmd.io/JOn9x98iS0e0DPWQ87zGWg?view + +use crate::node::{Node, NodeId, NodeRef, NodeRole}; +use crate::{ExtendedBalance, IdentifierT, StakedAssignment}; +use sp_runtime::traits::{Bounded, Zero}; +use sp_std::{ + collections::btree_map::{BTreeMap, Entry::*}, + prelude::*, +}; + +/// Map type used for reduce_4. Can be easily swapped with HashMap. +type Map = BTreeMap<(A, A), A>; + +/// Returns all combinations of size two in the collection `input` with no repetition. +fn combinations_2(input: &[T]) -> Vec<(T, T)> { + let n = input.len(); + if n < 2 { + return Default::default(); + } + + let mut comb = Vec::with_capacity(n * (n - 1) / 2); + for i in 0..n { + for j in i + 1..n { + comb.push((input[i].clone(), input[j].clone())) + } + } + comb +} + +/// Returns the count of trailing common elements in two slices. +pub(crate) fn trailing_common(t1: &[T], t2: &[T]) -> usize { + t1.iter().rev().zip(t2.iter().rev()).take_while(|e| e.0 == e.1).count() +} + +/// Merges two parent roots as described by the reduce algorithm. +fn merge(voter_root_path: Vec>, target_root_path: Vec>) { + let (shorter_path, longer_path) = if voter_root_path.len() <= target_root_path.len() { + (voter_root_path, target_root_path) + } else { + (target_root_path, voter_root_path) + }; + + // iterate from last to beginning, skipping the first one. This asserts that + // indexing is always correct. + shorter_path + .iter() + .zip(shorter_path.iter().skip(1)) + .for_each(|(voter, next)| Node::set_parent_of(&next, &voter)); + Node::set_parent_of(&shorter_path[0], &longer_path[0]); +} + +/// Reduce only redundant edges with cycle length of 4. +/// +/// Returns the number of edges removed. +/// +/// It is strictly assumed that the `who` attribute of all provided assignments are unique. The +/// result will most likely be corrupt otherwise. +/// +/// O(|E_w| ⋅ k). +fn reduce_4(assignments: &mut Vec>) -> u32 { + let mut combination_map: Map = Map::new(); + let mut num_changed: u32 = Zero::zero(); + + // we have to use the old fashioned loops here with manual indexing. Borrowing assignments will + // not work since then there is NO way to mutate it inside. + for assignment_index in 0..assignments.len() { + let who = assignments[assignment_index].who.clone(); + + // all combinations for this particular voter + let distribution_ids = &assignments[assignment_index] + .distribution + .iter() + .map(|(t, _p)| t.clone()) + .collect::>(); + let candidate_combinations = combinations_2(distribution_ids); + + for (v1, v2) in candidate_combinations { + match combination_map.entry((v1.clone(), v2.clone())) { + Vacant(entry) => { + entry.insert(who.clone()); + } + Occupied(mut entry) => { + let other_who = entry.get_mut(); + + // double check if who is still voting for this pair. If not, it means that this + // pair is no longer valid and must have been removed in previous rounds. The + // reason for this is subtle; candidate_combinations is created once while the + // inner loop might remove some edges. Note that if count() > 2, the we have + // duplicates. + if assignments[assignment_index] + .distribution + .iter() + .filter(|(t, _)| *t == v1 || *t == v2) + .count() != 2 + { + continue; + } + + // check if other_who voted for the same pair v1, v2. + let maybe_other_assignments = assignments.iter().find(|a| a.who == *other_who); + if maybe_other_assignments.is_none() { + continue; + } + let other_assignment = + maybe_other_assignments.expect("value is checked to be 'Some'"); + + // Collect potential cycle votes + let mut other_cycle_votes = other_assignment + .distribution + .iter() + .filter_map(|(t, w)| { + if *t == v1 || *t == v2 { + Some((t.clone(), *w)) + } else { + None + } + }) + .collect::>(); + + let other_votes_count = other_cycle_votes.len(); + + // If the length is more than 2, then we have identified duplicates. For now, we + // just skip. Later on we can early exit and stop processing this data since it + // is corrupt anyhow. + debug_assert!(other_votes_count <= 2); + + if other_votes_count < 2 { + // This is not a cycle. Replace and continue. + *other_who = who.clone(); + continue; + } else if other_votes_count == 2 { + // This is a cycle. + let mut who_cycle_votes: Vec<(A, ExtendedBalance)> = Vec::with_capacity(2); + assignments[assignment_index] + .distribution + .iter() + .for_each(|(t, w)| { + if *t == v1 || *t == v2 { + who_cycle_votes.push((t.clone(), *w)); + } + }); + + if who_cycle_votes.len() != 2 { + continue; + } + + // Align the targets similarly. This helps with the circulation below. + if other_cycle_votes[0].0 != who_cycle_votes[0].0 { + other_cycle_votes.swap(0, 1); + } + + // Find min + let mut min_value: ExtendedBalance = Bounded::max_value(); + let mut min_index: usize = 0; + let cycle = who_cycle_votes + .iter() + .chain(other_cycle_votes.iter()) + .enumerate() + .map(|(index, (t, w))| { + if *w <= min_value { + min_value = *w; + min_index = index; + } + (t.clone(), *w) + }) + .collect::>(); + + // min was in the first part of the chained iters + let mut increase_indices: Vec = Vec::new(); + let mut decrease_indices: Vec = Vec::new(); + decrease_indices.push(min_index); + if min_index < 2 { + // min_index == 0 => sibling_index <- 1 + // min_index == 1 => sibling_index <- 0 + let sibling_index = 1 - min_index; + increase_indices.push(sibling_index); + // valid because the two chained sections of `cycle` are aligned; + // index [0, 2] are both voting for v1 or both v2. Same goes for [1, 3]. + decrease_indices.push(sibling_index + 2); + increase_indices.push(min_index + 2); + } else { + // min_index == 2 => sibling_index <- 3 + // min_index == 3 => sibling_index <- 2 + let sibling_index = 3 - min_index % 2; + increase_indices.push(sibling_index); + // valid because the two chained sections of `cycle` are aligned; + // index [0, 2] are both voting for v1 or both v2. Same goes for [1, 3]. + decrease_indices.push(sibling_index - 2); + increase_indices.push(min_index - 2); + } + + // apply changes + let mut remove_indices: Vec = Vec::with_capacity(1); + increase_indices.into_iter().for_each(|i| { + let voter = if i < 2 { + who.clone() + } else { + other_who.clone() + }; + // Note: so this is pretty ambiguous. We should only look for one + // assignment that meets this criteria and if we find multiple then that + // is a corrupt input. Same goes for the next block. + assignments + .iter_mut() + .filter(|a| a.who == voter) + .for_each(|ass| { + ass.distribution + .iter_mut() + .position(|(t, _)| *t == cycle[i].0) + .map(|idx| { + let next_value = + ass.distribution[idx].1.saturating_add(min_value); + ass.distribution[idx].1 = next_value; + }); + }); + }); + decrease_indices.into_iter().for_each(|i| { + let voter = if i < 2 { + who.clone() + } else { + other_who.clone() + }; + assignments + .iter_mut() + .filter(|a| a.who == voter) + .for_each(|ass| { + ass.distribution + .iter_mut() + .position(|(t, _)| *t == cycle[i].0) + .map(|idx| { + let next_value = + ass.distribution[idx].1.saturating_sub(min_value); + if next_value.is_zero() { + ass.distribution.remove(idx); + remove_indices.push(i); + num_changed += 1; + } else { + ass.distribution[idx].1 = next_value; + } + }); + }); + }); + + // 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(); + + match (who_removed, other_removed) { + (false, true) => { + *other_who = who.clone(); + } + (true, false) => { + // nothing, other_who can stay there. + } + (true, true) => { + // remove and don't replace + entry.remove(); + } + (false, false) => { + // Neither of the edges was removed? impossible. + debug_assert!(false, "Duplicate voter (or other corrupt input)."); + } + } + } + } + } + } + } + + num_changed +} + +/// Reduce redundant edges from the edge weight graph, with all possible length. +/// +/// To get the best performance, this should be called after `reduce_4()`. +/// +/// Returns the number of edges removed. +/// +/// It is strictly assumed that the `who` attribute of all provided assignments are unique. The +/// result will most likely be corrupt otherwise. +/// +/// O(|Ew| ⋅ m) +fn reduce_all(assignments: &mut Vec>) -> u32 { + let mut num_changed: u32 = Zero::zero(); + let mut tree: BTreeMap, NodeRef> = BTreeMap::new(); + + // NOTE: This code can heavily use an index cache. Looking up a pair of (voter, target) in the + // assignments happens numerous times and and we can save time. For now it is written as such + // because abstracting some of this code into a function/closure is super hard due to borrow + // checks (and most likely needs unsafe code at the end). For now I will keep it as it and + // refactor later. + + // a flat iterator of (voter, target) over all pairs of votes. Similar to reduce_4, we loop + // without borrowing. + for assignment_index in 0..assignments.len() { + let voter = assignments[assignment_index].who.clone(); + + let mut dist_index = 0; + loop { + // A distribution could have been removed. We don't know for sure. Hence, we check. + let maybe_dist = assignments[assignment_index].distribution.get(dist_index); + if maybe_dist.is_none() { + // The rest of this loop is moot. + break; + } + let (target, _) = maybe_dist.expect("Value checked to be some").clone(); + + // store if they existed already. + let voter_id = NodeId::from(voter.clone(), NodeRole::Voter); + let target_id = NodeId::from(target.clone(), NodeRole::Target); + let voter_exists = tree.contains_key(&voter_id); + let target_exists = tree.contains_key(&target_id); + + // create both. + let voter_node = tree + .entry(voter_id.clone()) + .or_insert(Node::new(voter_id).into_ref()) + .clone(); + let target_node = tree + .entry(target_id.clone()) + .or_insert(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 + // 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) { + (false, false) => { + Node::set_parent_of(&target_node, &voter_node); + dist_index += 1; + continue; + } + (false, true) => { + Node::set_parent_of(&voter_node, &target_node); + dist_index += 1; + continue; + } + (true, false) => { + Node::set_parent_of(&target_node, &voter_node); + dist_index += 1; + continue; + } + (true, true) => { /* don't continue and execute the rest */ } + }; + + let (voter_root, voter_root_path) = Node::root(&voter_node); + let (target_root, target_root_path) = Node::root(&target_node); + + if voter_root != target_root { + // swap + merge(voter_root_path, target_root_path); + dist_index += 1; + } else { + // find common and cycle. + 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!(common_count > 0); + + // 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) + .cloned() + .chain( + voter_root_path + .iter() + .take(voter_root_path.len() - 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 index of the min in opaque cycle list. + let mut min_index = 0usize; + // 1 -> next // 0 -> prev + let mut min_direction = 0u32; + // helpers + let next_index = |i| { + if i < (cycle.len() - 1) { + i + 1 + } else { + 0 + } + }; + let prev_index = |i| { + if i > 0 { + i - 1 + } else { + 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 + let current = cycle[i].borrow().id.who.clone(); + let next = cycle[next_index(i)].borrow().id.who.clone(); + let prev = cycle[prev_index(i)].borrow().id.who.clone(); + assignments.iter().find(|a| a.who == current).map(|ass| { + 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; + } + }) + }); + assignments.iter().find(|a| a.who == current).map(|ass| { + 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; + } + }) + }); + } + } + + // if the min edge is in the voter's sub-chain. + // [target, ..., X, Y, ... voter] + let target_chunk = target_root_path.len() - common_count; + let min_chain_in_voter = (min_index + min_direction as usize) > target_chunk; + + // walk over the cycle and update the weights + let mut should_inc_counter = true; + let start_operation_add = ((min_index % 2) + min_direction as usize) % 2 == 1; + let mut additional_removed = Vec::new(); + for i in 0..cycle.len() { + let current = cycle[i].borrow(); + if current.id.role == NodeRole::Voter { + let prev = cycle[prev_index(i)].borrow(); + assignments + .iter_mut() + .enumerate() + .filter(|(_, a)| a.who == current.id.who) + .for_each(|(target_ass_index, ass)| { + ass.distribution + .iter_mut() + .position(|(t, _)| *t == prev.id.who) + .map(|idx| { + let next_value = if i % 2 == 0 { + if start_operation_add { + ass.distribution[idx].1.saturating_add(min_value) + } else { + 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) + } + }; + + if next_value.is_zero() { + // if the removed edge is from the current assignment, dis_index + // should NOT be increased. + if target_ass_index == assignment_index { + should_inc_counter = false + } + ass.distribution.remove(idx); + num_changed += 1; + // only add if this is not the min itself. + if !(i == min_index && min_direction == 0) { + additional_removed.push(( + cycle[i].clone(), + cycle[prev_index(i)].clone(), + )); + } + } else { + ass.distribution[idx].1 = next_value; + } + }); + }); + + let next = cycle[next_index(i)].borrow(); + assignments + .iter_mut() + .enumerate() + .filter(|(_, a)| a.who == current.id.who) + .for_each(|(target_ass_index, ass)| { + ass.distribution + .iter_mut() + .position(|(t, _)| *t == next.id.who) + .map(|idx| { + let next_value = if i % 2 == 0 { + if start_operation_add { + ass.distribution[idx].1.saturating_sub(min_value) + } else { + 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) + } + }; + + if next_value.is_zero() { + // if the removed edge is from the current assignment, dis_index + // should NOT be increased. + if target_ass_index == assignment_index { + should_inc_counter = false + } + ass.distribution.remove(idx); + num_changed += 1; + if !(i == min_index && min_direction == 1) { + additional_removed.push(( + cycle[i].clone(), + cycle[next_index(i)].clone(), + )); + } + } else { + ass.distribution[idx].1 = next_value; + } + }); + }); + } + } + + // don't do anything if the edge removed itself. This is always the first and last + // element + let should_reorg = !(min_index == (cycle.len() - 1) && min_direction == 1); + + // re-org. + if should_reorg { + let min_edge = vec![min_voter, min_target]; + if min_chain_in_voter { + // NOTE: safe; voter_root_path is always bigger than 1 element. + for i in 0..voter_root_path.len() - 1 { + let current = voter_root_path[i].clone().borrow().id.who.clone(); + let next = voter_root_path[i + 1].clone().borrow().id.who.clone(); + if min_edge.contains(¤t) && min_edge.contains(&next) { + break; + } + Node::set_parent_of(&voter_root_path[i + 1], &voter_root_path[i]); + } + Node::set_parent_of(&voter_node, &target_node); + } else { + // NOTE: safe; target_root_path is always bigger than 1 element. + for i in 0..target_root_path.len() - 1 { + let current = target_root_path[i].clone().borrow().id.who.clone(); + let next = target_root_path[i + 1].clone().borrow().id.who.clone(); + if min_edge.contains(¤t) && min_edge.contains(&next) { + break; + } + Node::set_parent_of(&target_root_path[i + 1], &target_root_path[i]); + } + Node::set_parent_of(&target_node, &voter_node); + } + } + + // remove every other node which has collapsed to zero + for (r1, r2) in additional_removed { + if Node::is_parent_of(&r1, &r2) { + Node::remove_parent(&r1); + } else if Node::is_parent_of(&r2, &r1) { + Node::remove_parent(&r2); + } + } + + // increment the counter if needed. + if should_inc_counter { + dist_index += 1; + } + } + } + } + + num_changed +} + +/// Reduce the given [`PhragmenResult`]. This removes redundant edges from without changing the +/// overall backing of any of the elected candidates. +/// +/// Returns the number of edges removed. +/// +/// IMPORTANT: It is strictly assumed that the `who` attribute of all provided assignments are +/// unique. The result will most likely be corrupt otherwise. Furthermore, if the _distribution +/// vector_ contains duplicate ids, only the first instance is ever updates. +/// +/// O(min{ |Ew| ⋅ k + m3 , |Ew| ⋅ m }) +pub fn reduce(assignments: &mut Vec>) -> u32 where { + let mut num_changed = reduce_4(assignments); + num_changed += reduce_all(assignments); + num_changed +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn merging_works() { + // D <-- A <-- B <-- C + // + // F <-- E + let d = Node::new(NodeId::from(1, NodeRole::Target)).into_ref(); + let a = Node::new(NodeId::from(2, NodeRole::Target)).into_ref(); + let b = Node::new(NodeId::from(3, NodeRole::Target)).into_ref(); + let c = Node::new(NodeId::from(4, NodeRole::Target)).into_ref(); + let e = Node::new(NodeId::from(5, NodeRole::Target)).into_ref(); + let f = Node::new(NodeId::from(6, NodeRole::Target)).into_ref(); + + Node::set_parent_of(&c, &b); + Node::set_parent_of(&b, &a); + Node::set_parent_of(&a, &d); + Node::set_parent_of(&e, &f); + + let path1 = vec![c.clone(), b.clone(), a.clone(), d.clone()]; + let path2 = vec![e.clone(), f.clone()]; + + merge(path1, path2); + // D <-- A <-- B <-- C + // | + // F --> E --> --> + assert_eq!(e.borrow().clone().parent.unwrap().borrow().id.who, 4u32); // c + } + + #[test] + fn merge_with_len_one() { + // D <-- A <-- B <-- C + // + // F <-- E + let d = Node::new(NodeId::from(1, NodeRole::Target)).into_ref(); + let a = Node::new(NodeId::from(2, NodeRole::Target)).into_ref(); + let b = Node::new(NodeId::from(3, NodeRole::Target)).into_ref(); + let c = Node::new(NodeId::from(4, NodeRole::Target)).into_ref(); + let f = Node::new(NodeId::from(6, NodeRole::Target)).into_ref(); + + Node::set_parent_of(&c, &b); + Node::set_parent_of(&b, &a); + Node::set_parent_of(&a, &d); + + let path1 = vec![c.clone(), b.clone(), a.clone(), d.clone()]; + let path2 = vec![f.clone()]; + + merge(path1, path2); + // D <-- A <-- B <-- C + // | + // F --> --> + assert_eq!(f.borrow().clone().parent.unwrap().borrow().id.who, 4u32); // c + } + + #[test] + fn basic_reduce_4_cycle_works() { + use super::*; + + let assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 25), (20, 75)], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 50), (20, 50)], + }, + ]; + + let mut new_assignments = assignments.clone(); + let num_reduced = reduce_4(&mut new_assignments); + + assert_eq!(num_reduced, 1); + assert_eq!( + new_assignments, + vec![ + StakedAssignment { + who: 1, + distribution: vec![(20, 100),], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 75), (20, 25),], + }, + ], + ); + } + + #[test] + fn basic_reduce_all_cycles_works() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10)], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5)], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 15), (40, 15)], + }, + StakedAssignment { + who: 4, + distribution: vec![(20, 10), (30, 10), (40, 20)], + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 20), (30, 10), (40, 20)], + }, + ]; + + assert_eq!(3, reduce_all(&mut assignments)); + + assert_eq!( + assignments, + vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10),] + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5),], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 30),], + }, + StakedAssignment { + who: 4, + distribution: vec![(40, 40),] + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 15), (30, 20), (40, 15),], + }, + ], + ) + } + + #[test] + fn basic_reduce_works() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10)], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5)], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 15), (40, 15)], + }, + StakedAssignment { + who: 4, + distribution: vec![(20, 10), (30, 10), (40, 20)], + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 20), (30, 10), (40, 20)], + }, + ]; + + assert_eq!(3, reduce(&mut assignments)); + + assert_eq!( + assignments, + vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10),] + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5),], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 30),], + }, + StakedAssignment { + who: 4, + distribution: vec![(40, 40),] + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 15), (30, 20), (40, 15),], + }, + ], + ) + } + + #[test] + fn should_deal_with_self_vote() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10)], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5)], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 15), (40, 15)], + }, + StakedAssignment { + who: 4, + distribution: vec![(20, 10), (30, 10), (40, 20)], + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 20), (30, 10), (40, 20)], + }, + // self vote from 10 and 20 to itself. + StakedAssignment { + who: 10, + distribution: vec![(10, 100)], + }, + StakedAssignment { + who: 20, + distribution: vec![(20, 200)], + }, + ]; + + assert_eq!(3, reduce(&mut assignments)); + + assert_eq!( + assignments, + vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10),] + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5),], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 30),], + }, + StakedAssignment { + who: 4, + distribution: vec![(40, 40),] + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 15), (30, 20), (40, 15),], + }, + // should stay untouched. + StakedAssignment { + who: 10, + distribution: vec![(10, 100)] + }, + StakedAssignment { + who: 20, + distribution: vec![(20, 200)] + }, + ], + ) + } + + #[test] + fn reduce_3_common_votes_same_weight() { + let mut assignments = vec![ + StakedAssignment { + who: 4, + distribution: vec![ + ( + 1000000, + 100, + ), + ( + 1000002, + 100, + ), + ( + 1000004, + 100, + ), + ], + }, + StakedAssignment { + who: 5, + distribution: vec![ + ( + 1000000, + 100, + ), + ( + 1000002, + 100, + ), + ( + 1000004, + 100, + ), + ], + }, + ]; + + reduce_4(&mut assignments); + + assert_eq!( + assignments, + vec![ + StakedAssignment { + who: 4, + distribution: vec![(1000000, 200,), (1000004, 100,),], + }, + StakedAssignment { + who: 5, + distribution: vec![(1000002, 200,), (1000004, 100,),], + }, + ], + ) + } + + #[test] + #[should_panic] + fn reduce_panics_on_duplicate_voter() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10), (20, 10)], + }, + StakedAssignment { + who: 1, + distribution: vec![(10, 15), (20, 5)], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 15)], + }, + ]; + + reduce(&mut assignments); + } + + #[test] + fn should_deal_with_duplicates_target() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 15), (20, 5)], + }, + StakedAssignment { + who: 2, + distribution: vec![ + (10, 15), + (20, 15), + // duplicate + (10, 1), + // duplicate + (20, 1), + ], + }, + ]; + + reduce(&mut assignments); + + assert_eq!( + assignments, + vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 20),], + }, + StakedAssignment { + who: 2, + distribution: vec![ + (10, 10), + (20, 20), + // duplicate votes are silently ignored. + (10, 1), + (20, 1), + ], + }, + ], + ) + } + + #[test] + fn bound_should_be_kept() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(103, 72), (101, 53), (100, 83), (102, 38)], + }, + StakedAssignment { + who: 2, + distribution: vec![(103, 18), (101, 36), (102, 54), (100, 94)], + }, + StakedAssignment { + who: 3, + distribution: vec![(100, 96), (101, 35), (102, 52), (103, 69)], + }, + StakedAssignment { + who: 4, + distribution: vec![(102, 34), (100, 47), (103, 91), (101, 73)], + }, + ]; + + let winners = vec![103, 101, 100, 102]; + + let n = 4; + let m = winners.len() as u32; + let num_reduced = reduce_all(&mut assignments); + assert!(16 - num_reduced <= n + m); + } +} diff --git a/primitives/phragmen/src/tests.rs b/primitives/phragmen/src/tests.rs index 9027cc335fce73a6ea1a4b163ae1b6823ef4b914..e9861ede723cf9f31baf5f6c5d91b79e361b75d9 100644 --- a/primitives/phragmen/src/tests.rs +++ b/primitives/phragmen/src/tests.rs @@ -19,11 +19,12 @@ #![cfg(test)] use crate::mock::*; -use crate::{elect, PhragmenResult, PhragmenStakedAssignment, build_support_map, Support, equalize}; +use crate::{ + elect, equalize, build_support_map, is_score_better, + Support, StakedAssignment, Assignment, PhragmenResult, ExtendedBalance, +}; use substrate_test_utils::assert_eq_uvec; -use sp_runtime::Perbill; - -type Output = Perbill; +use sp_runtime::{Perbill, Permill, Percent, PerU16, traits::Convert}; #[test] fn float_phragmen_poc_works() { @@ -80,21 +81,33 @@ fn phragmen_poc_works() { (30, vec![2, 3]), ]; - let PhragmenResult { winners, assignments } = elect::<_, _, _, TestCurrencyToVote, Output>( + let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]); + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates, - voters, - create_stake_of(&[(10, 10), (20, 20), (30, 30)]), + voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ).unwrap(); assert_eq_uvec!(winners, vec![(2, 40), (3, 50)]); assert_eq_uvec!( assignments, vec![ - (10, vec![(2, Perbill::from_percent(100))]), - (20, vec![(3, Perbill::from_percent(100))]), - (30, vec![(2, Perbill::from_percent(100/2)), (3, Perbill::from_percent(100/2))]), + Assignment { + who: 10u64, + distribution: vec![(2, Perbill::from_percent(100))], + }, + Assignment { + who: 20, + distribution: vec![(3, Perbill::from_percent(100))], + }, + Assignment { + who: 30, + distribution: vec![ + (2, Perbill::from_percent(100/2)), + (3, Perbill::from_percent(100/2)), + ], + }, ] ); } @@ -115,7 +128,10 @@ fn phragmen_poc_2_works() { (4, 500), ]); - run_and_compare(candidates, voters, stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates, voters, &stake_of, 2, 2); } #[test] @@ -133,7 +149,10 @@ fn phragmen_poc_3_works() { (4, 1000), ]); - run_and_compare(candidates, voters, stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates, voters, &stake_of, 2, 2); } #[test] @@ -149,17 +168,16 @@ fn phragmen_accuracy_on_large_scale_only_validators() { (5, (u64::max_value() - 2).into()), ]); - let PhragmenResult { winners, assignments } = elect::<_, _, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates.clone(), - auto_generate_self_voters(&candidates), - stake_of, + auto_generate_self_voters(&candidates).iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ).unwrap(); assert_eq_uvec!(winners, vec![(1, 18446744073709551614u128), (5, 18446744073709551613u128)]); assert_eq!(assignments.len(), 2); - check_assignments(assignments); + check_assignments_sum(assignments); } #[test] @@ -180,25 +198,36 @@ fn phragmen_accuracy_on_large_scale_validators_and_nominators() { (14, u64::max_value().into()), ]); - let PhragmenResult { winners, assignments } = elect::<_, _, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates, - voters, - stake_of, + voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ).unwrap(); assert_eq_uvec!(winners, vec![(2, 36893488147419103226u128), (1, 36893488147419103219u128)]); assert_eq!( assignments, vec![ - (13, vec![(1, Perbill::one())]), - (14, vec![(2, Perbill::one())]), - (1, vec![(1, Perbill::one())]), - (2, vec![(2, Perbill::one())]), + Assignment { + who: 13u64, + distribution: vec![(1, Perbill::one())], + }, + Assignment { + who: 14, + distribution: vec![(2, Perbill::one())], + }, + Assignment { + who: 1, + distribution: vec![(1, Perbill::one())], + }, + Assignment { + who: 2, + distribution: vec![(2, Perbill::one())], + }, ] ); - check_assignments(assignments); + check_assignments_sum(assignments); } #[test] @@ -212,12 +241,11 @@ fn phragmen_accuracy_on_small_scale_self_vote() { (30, 1), ]); - let PhragmenResult { winners, assignments: _ } = elect::<_, _, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>( 3, 3, candidates, - voters, - stake_of, + voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ).unwrap(); assert_eq_uvec!(winners, vec![(20, 2), (10, 1), (30, 1)]); @@ -243,12 +271,11 @@ fn phragmen_accuracy_on_small_scale_no_self_vote() { (3, 1), ]); - let PhragmenResult { winners, assignments: _ } = elect::<_, _, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>( 3, 3, candidates, - voters, - stake_of, + voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ).unwrap(); assert_eq_uvec!(winners, vec![(20, 2), (10, 1), (30, 1)]); @@ -277,16 +304,15 @@ fn phragmen_large_scale_test() { (50, 990000000000000000), ]); - let PhragmenResult { winners, assignments } = elect::<_, _, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates, - voters, - stake_of, + voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ).unwrap(); assert_eq_uvec!(winners, vec![(24, 1490000000000200000u128), (22, 1490000000000100000u128)]); - check_assignments(assignments); + check_assignments_sum(assignments); } #[test] @@ -304,24 +330,35 @@ fn phragmen_large_scale_test_2() { (50, nom_budget.into()), ]); - let PhragmenResult { winners, assignments } = elect::<_, _, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates, - voters, - stake_of, + voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ).unwrap(); assert_eq_uvec!(winners, vec![(2, 1000000000004000000u128), (4, 1000000000004000000u128)]); assert_eq!( assignments, vec![ - (50, vec![(2, Perbill::from_parts(500000001)), (4, Perbill::from_parts(499999999))]), - (2, vec![(2, Perbill::one())]), - (4, vec![(4, Perbill::one())]), + Assignment { + who: 50u64, + distribution: vec![ + (2, Perbill::from_parts(500000001)), + (4, Perbill::from_parts(499999999)) + ], + }, + Assignment { + who: 2, + distribution: vec![(2, Perbill::one())], + }, + Assignment { + who: 4, + distribution: vec![(4, Perbill::one())], + }, ], ); - check_assignments(assignments); + check_assignments_sum(assignments); } #[test] @@ -354,7 +391,7 @@ fn phragmen_linear_equalize() { (130, 1000), ]); - run_and_compare(candidates, voters, stake_of, 2, 2); + run_and_compare::(candidates, voters, &stake_of, 2, 2); } #[test] @@ -369,12 +406,11 @@ fn elect_has_no_entry_barrier() { (2, 10), ]); - let PhragmenResult { winners, assignments: _ } = elect::<_, _, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>( 3, 3, candidates, - voters, - stake_of, + voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ).unwrap(); // 30 is elected with stake 0. The caller is responsible for stripping this. @@ -397,12 +433,11 @@ fn minimum_to_elect_is_respected() { (2, 10), ]); - let maybe_result = elect::<_, _, _, TestCurrencyToVote, Output>( + let maybe_result = elect::<_, _, TestCurrencyToVote, Perbill>( 10, 10, candidates, - voters, - stake_of, + voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ); assert!(maybe_result.is_none()); @@ -424,39 +459,39 @@ fn self_votes_should_be_kept() { (1, 8), ]); - let result = elect::<_, _, _, TestCurrencyToVote, Output>( + let result = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates, - voters, - &stake_of, + voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::>(), ).unwrap(); assert_eq!(result.winners, vec![(20, 28), (10, 18)]); assert_eq!( result.assignments, vec![ - (10, vec![(10, Perbill::from_percent(100))]), - (20, vec![(20, Perbill::from_percent(100))]), - (1, vec![ + Assignment { who: 10, distribution: vec![(10, Perbill::from_percent(100))] }, + Assignment { who: 20, distribution: vec![(20, Perbill::from_percent(100))] }, + Assignment { who: 1, distribution: vec![ (10, Perbill::from_percent(50)), (20, Perbill::from_percent(50)) ] - ) + }, ], ); - let mut supports = build_support_map::< - Balance, - AccountId, - _, - TestCurrencyToVote, - Output, - >( - &result.winners.into_iter().map(|(who, _)| who).collect(), - &result.assignments, - &stake_of - ); + let staked_assignments: Vec> = result.assignments + .into_iter() + .map(|a| { + let stake = >::convert(stake_of(&a.who)) as ExtendedBalance; + a.into_staked(stake, true) + }).collect(); + + let winners = result.winners.into_iter().map(|(who, _)| who).collect::>(); + let (mut supports, _) = build_support_map::( + winners.as_slice(), + &staked_assignments, + ); assert_eq!(supports.get(&5u64), None); assert_eq!( @@ -468,23 +503,13 @@ fn self_votes_should_be_kept() { &Support { total: 24u128, voters: vec![(20u64, 20u128), (1u64, 4u128)] }, ); - let assignments = result.assignments; - let mut staked_assignments - : Vec<(AccountId, Vec>)> - = Vec::with_capacity(assignments.len()); - for (n, assignment) in assignments.iter() { - let mut staked_assignment - : Vec> - = Vec::with_capacity(assignment.len()); - let stake = stake_of(&n); - for (c, per_thing) in assignment.iter() { - let vote_stake = *per_thing * stake; - staked_assignment.push((c.clone(), vote_stake)); - } - staked_assignments.push((n.clone(), staked_assignment)); - } - - equalize::(staked_assignments, &mut supports, 0, 2usize, &stake_of); + equalize::( + staked_assignments, + &mut supports, + 0, + 2usize, + &stake_of, + ); assert_eq!( supports.get(&10u64).unwrap(), @@ -495,3 +520,462 @@ fn self_votes_should_be_kept() { &Support { total: 20u128, voters: vec![(20u64, 20u128)] }, ); } + +#[test] +fn assignment_convert_works() { + let staked = StakedAssignment { + who: 1 as AccountId, + distribution: vec![ + (20, 100 as Balance), + (30, 25), + ], + }; + + let assignment = staked.clone().into_assignment(true); + assert_eq!( + assignment, + Assignment { + who: 1, + distribution: vec![ + (20, Perbill::from_percent(80)), + (30, Perbill::from_percent(20)), + ] + } + ); + + assert_eq!( + assignment.into_staked(125, true), + staked, + ); +} + +#[test] +fn score_comparison_is_lexicographical() { + // only better in the fist parameter, worse in the other two ✅ + assert_eq!( + is_score_better([10, 20, 30], [12, 10, 35]), + true, + ); + + // worse in the first, better in the other two ❌ + assert_eq!( + is_score_better([10, 20, 30], [9, 30, 10]), + false, + ); + + // equal in the first, the second one dictates. + assert_eq!( + is_score_better([10, 20, 30], [10, 25, 40]), + true, + ); + + // equal in the first two, the last one dictates. + assert_eq!( + is_score_better([10, 20, 30], [10, 20, 40]), + false, + ); +} + +mod compact { + use codec::{Decode, Encode}; + use crate::generate_compact_solution_type; + use super::{AccountId, Balance}; + // these need to come from the same dev-dependency `sp-phragmen`, not from the crate. + use sp_phragmen::{Assignment, StakedAssignment, Error as PhragmenError}; + use sp_std::{convert::{TryInto, TryFrom}, fmt::Debug}; + use sp_runtime::Percent; + + type Accuracy = Percent; + + generate_compact_solution_type!(TestCompact, 16); + + #[test] + fn compact_struct_is_codec() { + let compact = TestCompact::<_, _, _> { + votes1: vec![(2u64, 20), (4, 40)], + votes2: vec![ + (1, (10, Accuracy::from_percent(80)), 11), + (5, (50, Accuracy::from_percent(85)), 51), + ], + ..Default::default() + }; + + let encoded = compact.encode(); + + assert_eq!( + compact, + Decode::decode(&mut &encoded[..]).unwrap(), + ); + } + + fn basic_ratio_test_with() where + V: codec::Codec + Copy + Default + PartialEq + Eq + TryInto + TryFrom + From + Debug, + T: codec::Codec + Copy + Default + PartialEq + Eq + TryInto + TryFrom + From + Debug, + >::Error: std::fmt::Debug, + >::Error: std::fmt::Debug, + >::Error: std::fmt::Debug, + >::Error: std::fmt::Debug, + { + 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, Accuracy::from_percent(100))] + }, + Assignment { + who: 4, + distribution: vec![(40, Accuracy::from_percent(100))], + }, + Assignment { + who: 1, + distribution: vec![ + (10, Accuracy::from_percent(80)), + (11, Accuracy::from_percent(20)) + ], + }, + Assignment { + who: 5, + distribution: vec![ + (50, Accuracy::from_percent(85)), + (51, Accuracy::from_percent(15)), + ] + }, + Assignment { + who: 3, + distribution: vec![ + (30, Accuracy::from_percent(50)), + (31, Accuracy::from_percent(25)), + (32, Accuracy::from_percent(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 compacted = >::from_assignment( + assignments.clone(), + voter_index, + target_index, + ).unwrap(); + + assert_eq!( + compacted, + TestCompact { + votes1: vec![(V::from(0u8), T::from(2u8)), (V::from(1u8), T::from(6u8))], + votes2: vec![ + (V::from(2u8), (T::from(0u8), Accuracy::from_percent(80)), T::from(1u8)), + (V::from(3u8), (T::from(7u8), Accuracy::from_percent(85)), T::from(8u8)), + ], + votes3: vec![ + ( + V::from(4), + [(T::from(3u8), Accuracy::from_percent(50)), (T::from(4u8), Accuracy::from_percent(25))], + T::from(5u8), + ), + ], + ..Default::default() + } + ); + + let voter_at = |a: V| -> Option { voters.get(>::try_into(a).unwrap()).cloned() }; + let target_at = |a: T| -> Option { targets.get(>::try_into(a).unwrap()).cloned() }; + + assert_eq!( + compacted.into_assignment(voter_at, target_at).unwrap(), + assignments, + ); + } + + #[test] + fn basic_from_and_into_compact_works_assignments() { + basic_ratio_test_with::(); + basic_ratio_test_with::(); + basic_ratio_test_with::(); + } + + #[test] + fn basic_from_and_into_compact_works_staked_assignments() { + let voters = vec![ + 2 as AccountId, + 4, + 1, + 5, + 3, + ]; + let targets = vec![ + 10 as AccountId, 11, + 20, + 30, 31, 32, + 40, + 50, 51, + ]; + + let assignments = vec![ + StakedAssignment { + who: 2 as AccountId, + distribution: vec![(20, 100 as Balance)] + }, + StakedAssignment { + who: 4, + distribution: vec![(40, 100)], + }, + StakedAssignment { + who: 1, + distribution: vec![ + (10, 80), + (11, 20) + ], + }, + StakedAssignment { + who: 5, distribution: + vec![ + (50, 85), + (51, 15), + ] + }, + StakedAssignment { + who: 3, + distribution: vec![ + (30, 50), + (31, 25), + (32, 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 compacted = >::from_staked( + assignments.clone(), + voter_index, + target_index, + ).unwrap(); + + assert_eq!( + compacted, + TestCompact { + votes1: vec![(0, 2), (1, 6)], + votes2: vec![ + (2, (0, 80), 1), + (3, (7, 85), 8), + ], + votes3: vec![ + (4, [(3, 50), (4, 25)], 5), + ], + ..Default::default() + } + ); + + let max_of_fn = |_: &AccountId| -> Balance { 100u128 }; + let voter_at = |a: u16| -> Option { voters.get(a as usize).cloned() }; + let target_at = |a: u16| -> Option { targets.get(a as usize).cloned() }; + + assert_eq!( + compacted.into_staked( + max_of_fn, + voter_at, + target_at, + ).unwrap(), + assignments, + ); + } + + #[test] + fn compact_into_stake_must_report_overflow() { + // The last edge which is computed from the rest should ALWAYS be positive. + // in votes2 + let compact = TestCompact:: { + votes1: Default::default(), + votes2: vec![(0, (1, 10), 2)], + ..Default::default() + }; + + let entity_at = |a: u16| -> Option { Some(a as AccountId) }; + let max_of = |_: &AccountId| -> Balance { 5 }; + + assert_eq!( + compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(), + PhragmenError::CompactStakeOverflow, + ); + + // in votes3 onwards + let compact = TestCompact:: { + votes1: Default::default(), + votes2: Default::default(), + votes3: vec![(0, [(1, 7), (2, 8)], 3)], + ..Default::default() + }; + + assert_eq!( + compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(), + PhragmenError::CompactStakeOverflow, + ); + + // Also if equal + let compact = TestCompact:: { + votes1: Default::default(), + votes2: Default::default(), + // 5 is total, we cannot leave none for 30 here. + votes3: vec![(0, [(1, 3), (2, 2)], 3)], + ..Default::default() + }; + + assert_eq!( + compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(), + PhragmenError::CompactStakeOverflow, + ); + } + + #[test] + fn compact_into_assignment_must_report_overflow() { + // in votes2 + let compact = TestCompact:: { + votes1: Default::default(), + votes2: vec![(0, (1, Accuracy::from_percent(100)), 2)], + ..Default::default() + }; + + let entity_at = |a: u16| -> Option { Some(a as AccountId) }; + + assert_eq!( + compact.into_assignment(&entity_at, &entity_at).unwrap_err(), + PhragmenError::CompactStakeOverflow, + ); + + // in votes3 onwards + let compact = TestCompact:: { + votes1: Default::default(), + votes2: Default::default(), + votes3: vec![(0, [(1, Accuracy::from_percent(70)), (2, Accuracy::from_percent(80))], 3)], + ..Default::default() + }; + + assert_eq!( + compact.into_assignment(&entity_at, &entity_at).unwrap_err(), + PhragmenError::CompactStakeOverflow, + ); + } + + #[test] + fn target_count_overflow_is_detected() { + let assignments = vec![ + StakedAssignment { + who: 1 as AccountId, + distribution: (10..26).map(|i| (i as AccountId, i as Balance)).collect::>(), + }, + ]; + + let entity_index = |a: &AccountId| -> Option { Some(*a as u16) }; + + let compacted = >::from_staked( + assignments.clone(), + entity_index, + entity_index, + ); + + assert!(compacted.is_ok()); + + let assignments = vec![ + StakedAssignment { + who: 1 as AccountId, + distribution: (10..27).map(|i| (i as AccountId, i as Balance)).collect::>(), + }, + ]; + + let compacted = >::from_staked( + assignments.clone(), + entity_index, + entity_index, + ); + + assert_eq!( + compacted.unwrap_err(), + PhragmenError::CompactTargetOverflow, + ); + + let assignments = vec![ + Assignment { + who: 1 as AccountId, + distribution: (10..27).map(|i| (i as AccountId, Percent::from_parts(i as u8))).collect::>(), + }, + ]; + + let compacted = >::from_assignment( + assignments.clone(), + entity_index, + entity_index, + ); + + assert_eq!( + compacted.unwrap_err(), + PhragmenError::CompactTargetOverflow, + ); + } + + #[test] + fn zero_target_count_is_ignored() { + let voters = vec![1 as AccountId, 2]; + let targets = vec![10 as AccountId, 11]; + + let assignments = vec![ + StakedAssignment { + who: 1 as AccountId, + distribution: vec![(10, 100 as Balance), (11, 100)] + }, + StakedAssignment { + 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 compacted = >::from_staked( + assignments.clone(), + voter_index, + target_index, + ).unwrap(); + + assert_eq!( + compacted, + TestCompact { + votes1: Default::default(), + votes2: vec![(0, (0, 100), 1)], + ..Default::default() + } + ); + } +} diff --git a/primitives/rpc/Cargo.toml b/primitives/rpc/Cargo.toml index 13d91e71d34e47e51644b61fdac38d681b4beae4..a7975fb4754b9ee049b7f76f45ca0876a4b91006 100644 --- a/primitives/rpc/Cargo.toml +++ b/primitives/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-rpc" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,7 +10,10 @@ description = "Substrate RPC primitives and utilities." [dependencies] serde = { version = "1.0.101", features = ["derive"] } -sp-core = { version = "2.0.0-alpha.2", path = "../core" } +sp-core = { version = "2.0.0-alpha.5", path = "../core" } [dev-dependencies] serde_json = "1.0.41" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/runtime-interface/Cargo.toml b/primitives/runtime-interface/Cargo.toml index 70092c0587fe993e758e78dd74f1423ec204d803..01f7f2f19c98169dbebd86e841fd37473ea6d011 100644 --- a/primitives/runtime-interface/Cargo.toml +++ b/primitives/runtime-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-runtime-interface" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,19 +10,19 @@ description = "Substrate runtime interface" documentation = "https://docs.rs/sp-runtime-interface/" [dependencies] -sp-wasm-interface = { version = "2.0.0-alpha.2", path = "../wasm-interface", default-features = false } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-runtime-interface-proc-macro = { version = "2.0.0-alpha.2", path = "proc-macro" } -sp-externalities = { version = "0.8.0-alpha.2", optional = true, path = "../externalities" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } +sp-wasm-interface = { version = "2.0.0-alpha.5", path = "../wasm-interface", default-features = false } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-runtime-interface-proc-macro = { version = "2.0.0-alpha.5", path = "proc-macro" } +sp-externalities = { version = "0.8.0-alpha.5", optional = true, path = "../externalities" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } static_assertions = "1.0.0" -primitive-types = { version = "0.6.2", default-features = false } +primitive-types = { version = "0.7.0", default-features = false } [dev-dependencies] sp-runtime-interface-test-wasm = { version = "2.0.0-dev", path = "test-wasm" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../primitives/state-machine" } -sp-core = { version = "2.0.0-alpha.2", path = "../core" } -sp-io = { version = "2.0.0-alpha.2", path = "../io" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../primitives/state-machine" } +sp-core = { version = "2.0.0-alpha.5", path = "../core" } +sp-io = { version = "2.0.0-alpha.5", path = "../io" } rustversion = "1.0.0" trybuild = "1.0.23" @@ -43,3 +43,6 @@ std = [ # Disables static assertions in `impls.rs` that checks the word size. To prevent any footgun, the # check is changed into a runtime check. disable_target_static_assertions = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/runtime-interface/proc-macro/Cargo.toml b/primitives/runtime-interface/proc-macro/Cargo.toml index 3743b2e09a153d5734250b131b56125040516194..6d0b7ee5fb7262c4a55922c0b1cef6549424c935 100644 --- a/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/primitives/runtime-interface/proc-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-runtime-interface-proc-macro" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -18,3 +18,6 @@ quote = "1.0.3" proc-macro2 = "1.0.3" Inflector = "0.11.4" proc-macro-crate = "0.1.4" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] 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 bdddc5eba70e79f1ebf908acc3ec9f83bf6cceb1..e7c34fbf9934211df7ea874e08d2d4382b1c7fe2 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 @@ -30,7 +30,7 @@ use crate::utils::{ generate_crate_access, create_exchangeable_host_function_ident, get_function_arguments, - get_function_argument_names, get_trait_methods, + get_function_argument_names, get_runtime_interface, create_function_ident_with_version, }; use syn::{ @@ -47,19 +47,40 @@ use std::iter; /// of the trait method. pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result { let trait_name = &trait_def.ident; - get_trait_methods(trait_def).try_fold(TokenStream::new(), |mut t, m| { - t.extend(function_for_method(trait_name, m, is_wasm_only)?); + let runtime_interface = get_runtime_interface(trait_def)?; + + // latest version dispatch + let token_stream: Result = runtime_interface.latest_versions() + .try_fold( + TokenStream::new(), + |mut t, (latest_version, method)| { + t.extend(function_for_method(method, latest_version, is_wasm_only)?); + Ok(t) + } + ); + + // earlier versions compatibility dispatch (only std variant) + let result: Result = runtime_interface.all_versions().try_fold(token_stream?, |mut t, (version, method)| + { + t.extend(function_std_impl(trait_name, method, version, is_wasm_only)?); Ok(t) - }) + }); + + result } /// Generates the bare function implementation for the given method for the host and wasm side. fn function_for_method( - trait_name: &Ident, method: &TraitItemMethod, + latest_version: u32, is_wasm_only: bool, ) -> Result { - let std_impl = function_std_impl(trait_name, method, is_wasm_only)?; + 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)?; Ok( @@ -78,7 +99,7 @@ fn function_no_std_impl(method: &TraitItemMethod) -> Result { let args = get_function_arguments(&method.sig); let arg_names = get_function_argument_names(&method.sig); let return_value = &method.sig.output; - let attrs = &method.attrs; + let attrs = method.attrs.iter().filter(|a| !a.path.is_ident("version")); Ok( quote! { @@ -92,13 +113,40 @@ fn function_no_std_impl(method: &TraitItemMethod) -> Result { ) } +/// Generate call to latest function version for `cfg((feature = "std")` +/// +/// This should generate simple `fn func(..) { func_version_(..) }`. +fn function_std_latest_impl( + method: &TraitItemMethod, + latest_version: u32, +) -> Result { + let function_name = &method.sig.ident; + let args = get_function_arguments(&method.sig).map(FnArg::Typed); + let arg_names = get_function_argument_names(&method.sig).collect::>(); + let return_value = &method.sig.output; + let attrs = method.attrs.iter().filter(|a| !a.path.is_ident("version")); + let latest_function_name = create_function_ident_with_version(&method.sig.ident, latest_version); + + Ok(quote_spanned! { method.span() => + #[cfg(feature = "std")] + #( #attrs )* + pub fn #function_name( #( #args, )* ) #return_value { + #latest_function_name( + #( #arg_names, )* + ) + } + }) +} + /// Generates the bare function implementation for `cfg(feature = "std")`. fn function_std_impl( trait_name: &Ident, method: &TraitItemMethod, + version: u32, is_wasm_only: bool, ) -> Result { - let function_name = &method.sig.ident; + let function_name = create_function_ident_with_version(&method.sig.ident, version); + let crate_ = generate_crate_access(); let args = get_function_arguments(&method.sig).map(FnArg::Typed).chain( // Add the function context as last parameter when this is a wasm only interface. @@ -115,16 +163,15 @@ fn function_std_impl( ).take(1), ); let return_value = &method.sig.output; - let attrs = &method.attrs; + let attrs = method.attrs.iter().filter(|a| !a.path.is_ident("version")); // Don't make the function public accessible when this is a wasm only interface. - let vis = if is_wasm_only { quote!() } else { quote!(pub) }; - let call_to_trait = generate_call_to_trait(trait_name, method, is_wasm_only); + let call_to_trait = generate_call_to_trait(trait_name, method, version, is_wasm_only); Ok( quote_spanned! { method.span() => #[cfg(feature = "std")] #( #attrs )* - #vis fn #function_name( #( #args, )* ) #return_value { + fn #function_name( #( #args, )* ) #return_value { #call_to_trait } } @@ -135,10 +182,11 @@ fn function_std_impl( fn generate_call_to_trait( trait_name: &Ident, method: &TraitItemMethod, + version: u32, is_wasm_only: bool, ) -> TokenStream { let crate_ = generate_crate_access(); - let method_name = &method.sig.ident; + let method_name = create_function_ident_with_version(&method.sig.ident, version); let expect_msg = format!( "`{}` called outside of an Externalities-provided environment.", method_name, 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 42bfe3c712b9eabf2caefef70f252aaf3f24ce18..205ee87105c41c9a1d5166f2a6b182ae28dd308b 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 @@ -23,13 +23,13 @@ use crate::utils::{ generate_crate_access, create_host_function_ident, get_function_argument_names, get_function_argument_types_without_ref, get_function_argument_types_ref_and_mut, - get_function_argument_names_and_types_without_ref, get_trait_methods, get_function_arguments, - get_function_argument_types, create_exchangeable_host_function_ident, + get_function_argument_names_and_types_without_ref, get_function_arguments, + get_function_argument_types, create_exchangeable_host_function_ident, get_runtime_interface, + create_function_ident_with_version, }; use syn::{ - ItemTrait, TraitItemMethod, Result, ReturnType, Ident, TraitItem, Pat, Error, Signature, - spanned::Spanned, + ItemTrait, TraitItemMethod, Result, ReturnType, Ident, Pat, Error, Signature, spanned::Spanned, }; use proc_macro2::{TokenStream, Span}; @@ -44,13 +44,15 @@ use std::iter::{Iterator, self}; /// 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_trait_methods(trait_def) - .try_fold(TokenStream::new(), |mut t, m| { - t.extend(generate_extern_host_function(m, trait_name)?); + let extern_host_function_impls = get_runtime_interface(trait_def)? + .latest_versions() + .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_trait_methods(trait_def) - .try_fold(TokenStream::new(), |mut t, m| { + let exchangeable_host_functions = get_runtime_interface(trait_def)? + .latest_versions() + .try_fold(TokenStream::new(), |mut t, (_, m)| { t.extend(generate_exchangeable_host_function(m)?); Ok::<_, Error>(t) })?; @@ -76,7 +78,7 @@ pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result Result { +fn generate_extern_host_function(method: &TraitItemMethod, version: u32, trait_name: &Ident) -> Result { let crate_ = generate_crate_access(); let args = get_function_arguments(&method.sig); let arg_types = get_function_argument_types_without_ref(&method.sig); @@ -85,7 +87,7 @@ fn generate_extern_host_function(method: &TraitItemMethod, trait_name: &Ident) - let arg_names2 = get_function_argument_names(&method.sig); let arg_names3 = get_function_argument_names(&method.sig); let function = &method.sig.ident; - let ext_function = create_host_function_ident(&method.sig.ident, trait_name); + let ext_function = create_host_function_ident(&method.sig.ident, version, trait_name); let doc_string = format!( " Default extern host function implementation for [`super::{}`].", method.sig.ident, @@ -157,14 +159,12 @@ fn generate_exchangeable_host_function(method: &TraitItemMethod) -> Result Result { let crate_ = generate_crate_access(); - let host_functions = trait_def - .items - .iter() - .filter_map(|i| match i { - TraitItem::Method(ref method) => Some(method), - _ => None, - }) - .map(|m| generate_host_function_implementation(&trait_def.ident, m, is_wasm_only)) + + 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::>>()?; Ok( @@ -191,9 +191,10 @@ fn generate_host_functions_struct(trait_def: &ItemTrait, is_wasm_only: bool) -> fn generate_host_function_implementation( trait_name: &Ident, method: &TraitItemMethod, + version: u32, is_wasm_only: bool, ) -> Result { - let name = create_host_function_ident(&method.sig.ident, trait_name).to_string(); + 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)?; @@ -202,7 +203,7 @@ fn generate_host_function_implementation( 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, is_wasm_only); + 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); @@ -211,7 +212,6 @@ fn generate_host_function_implementation( { struct #struct_name; - #[allow(unused)] impl #crate_::sp_wasm_interface::Function for #struct_name { fn name(&self) -> &str { #name @@ -322,8 +322,8 @@ fn generate_ffi_to_host_value<'a>( } /// Generate the code to call the host function and the ident that stores the result. -fn generate_host_function_call(sig: &Signature, is_wasm_only: bool) -> TokenStream { - let host_function_name = &sig.ident; +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)) 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 af71ba324172157be76ee094b6682fe880bc1b2d..542c4ca4b8c37b3519a4bb103897c80d83145b4d 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 @@ -17,10 +17,15 @@ //! Checks the trait declaration, makes the trait declaration module local, removes all method //! default implementations and implements the trait for `&mut dyn Externalities`. -use crate::utils::{generate_crate_access, get_function_argument_types_without_ref}; +use crate::utils::{ + generate_crate_access, + get_function_argument_types_without_ref, + get_runtime_interface, + create_function_ident_with_version, +}; use syn::{ - ItemTrait, TraitItemMethod, Result, TraitItem, Error, fold::{self, Fold}, spanned::Spanned, + ItemTrait, TraitItemMethod, Result, Error, fold::{self, Fold}, spanned::Spanned, Visibility, Receiver, Type, Generics, }; @@ -32,7 +37,7 @@ use quote::quote; /// essential definition and implement this essential definition for `dyn Externalities`. pub fn process(trait_def: &ItemTrait, is_wasm_only: bool) -> Result { let impl_trait = impl_trait_for_externalities(trait_def, is_wasm_only)?; - let essential_trait_def = ToEssentialTraitDef::convert(trait_def.clone())?; + let essential_trait_def = declare_essential_trait(trait_def)?; Ok( quote! { @@ -48,29 +53,35 @@ pub fn process(trait_def: &ItemTrait, is_wasm_only: bool) -> Result struct ToEssentialTraitDef { /// All errors found while doing the conversion. errors: Vec, + methods: Vec, } impl ToEssentialTraitDef { - /// Convert the given trait definition to the essential trait definition. - fn convert(trait_def: ItemTrait) -> Result { - let mut folder = ToEssentialTraitDef { - errors: Vec::new(), - }; - - let res = folder.fold_item_trait(trait_def); + fn new() -> Self { + ToEssentialTraitDef { errors: vec![], methods: vec![] } + } - if let Some(first_error) = folder.errors.pop() { + fn into_methods(self) -> Result> { + let mut errors = self.errors; + let methods = self.methods; + if let Some(first_error) = errors.pop() { Err( - folder.errors.into_iter().fold(first_error, |mut o, n| { + errors.into_iter().fold(first_error, |mut o, n| { o.combine(n); o }) ) } else { - Ok(res) + Ok(methods) } } + fn process(&mut self, method: &TraitItemMethod, version: u32) { + let mut folded = self.fold_trait_item_method(method.clone()); + folded.sig.ident = create_function_ident_with_version(&folded.sig.ident, version); + self.methods.push(folded); + } + fn push_error(&mut self, span: &S, msg: &str) { self.errors.push(Error::new(span.span(), msg)); } @@ -98,6 +109,8 @@ impl Fold for ToEssentialTraitDef { self.error_on_generic_parameters(&method.sig.generics); + method.attrs.retain(|a| !a.path.is_ident("version")); + fold::fold_trait_item_method(self, method) } @@ -117,17 +130,40 @@ impl Fold for ToEssentialTraitDef { } } +fn declare_essential_trait(trait_def: &ItemTrait) -> Result { + let trait_ = &trait_def.ident; + + if let Some(param) = trait_def.generics.params.first() { + return Err(Error::new(param.span(), "Generic parameters not supported.")) + } + + let interface = get_runtime_interface(trait_def)?; + let mut folder = ToEssentialTraitDef::new(); + for (version, interface_method) in interface.all_versions() { + folder.process(interface_method, version); + } + let methods = folder.into_methods()?; + + Ok( + quote! { + trait #trait_ { + #( #methods )* + } + } + ) +} + /// Implements the given trait definition for `dyn Externalities`. fn impl_trait_for_externalities(trait_def: &ItemTrait, is_wasm_only: bool) -> Result { let trait_ = &trait_def.ident; let crate_ = generate_crate_access(); - let methods = trait_def - .items - .iter() - .filter_map(|i| match i { - TraitItem::Method(ref method) => Some(method), - _ => None, - }); + let interface = get_runtime_interface(trait_def)?; + let methods = interface.all_versions().map(|(version, method)| { + 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 + }); let impl_type = if is_wasm_only { quote!( &mut dyn #crate_::sp_wasm_interface::FunctionContext ) diff --git a/primitives/runtime-interface/proc-macro/src/utils.rs b/primitives/runtime-interface/proc-macro/src/utils.rs index 15c65f11caf2932bb1c77d51233164f69045b58f..45f66e3bf6525569d9ed6c5f9209e350bbe97cd2 100644 --- a/primitives/runtime-interface/proc-macro/src/utils.rs +++ b/primitives/runtime-interface/proc-macro/src/utils.rs @@ -20,17 +20,60 @@ use proc_macro2::{TokenStream, Span}; use syn::{ Ident, Error, Signature, Pat, PatType, FnArg, Type, token, TraitItemMethod, ItemTrait, - TraitItem, parse_quote, spanned::Spanned, + TraitItem, parse_quote, spanned::Spanned, Result, Meta, NestedMeta, Lit, Attribute, }; use proc_macro_crate::crate_name; use std::env; +use std::collections::{BTreeMap, btree_map::Entry}; 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, +} + +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 + }, + } + } + + 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") + ) + } +} + +/// All functions of a runtime interface grouped by the function names. +pub struct RuntimeInterface<'a> { + items: BTreeMap>, +} + +impl<'a> RuntimeInterface<'a> { + pub fn latest_versions(&self) -> impl Iterator { + self.items.iter().map(|(_, item)| item.latest_version()) + } + + pub fn all_versions(&self) -> impl Iterator { + self.items.iter().flat_map(|(_, item)| item.versions.iter()).map(|(v, i)| (*v, *i)) + } + } + /// Generates the include for the runtime-interface crate. pub fn generate_runtime_interface_include() -> TokenStream { if env::var("CARGO_PKG_NAME").unwrap() == "sp-runtime-interface" { @@ -67,12 +110,25 @@ pub fn create_exchangeable_host_function_ident(name: &Ident) -> Ident { } /// Create the host function identifier for the given function name. -pub fn create_host_function_ident(name: &Ident, trait_name: &Ident) -> Ident { +pub fn create_host_function_ident(name: &Ident, version: u32, trait_name: &Ident) -> Ident { Ident::new( &format!( - "ext_{}_{}_version_1", + "ext_{}_{}_version_{}", trait_name.to_string().to_snake_case(), name, + version, + ), + Span::call_site(), + ) +} + +/// Create the host function identifier for the given function name. +pub fn create_function_ident_with_version(name: &Ident, version: u32) -> Ident { + Ident::new( + &format!( + "{}_version_{}", + name, + version, ), Span::call_site(), ) @@ -151,7 +207,7 @@ pub fn get_function_argument_types_ref_and_mut<'a>( } /// Returns an iterator over all trait methods for the given trait definition. -pub fn get_trait_methods<'a>(trait_def: &'a ItemTrait) -> impl Iterator { +fn get_trait_methods<'a>(trait_def: &'a ItemTrait) -> impl Iterator { trait_def .items .iter() @@ -160,3 +216,85 @@ pub fn get_trait_methods<'a>(trait_def: &'a ItemTrait) -> impl Iterator None, }) } + +/// Parse version attribute. +/// +/// 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, + } +} + +/// Return item version (`#[version(X)]`) attribute, 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)) + .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(); + + for item in get_trait_methods(trait_def) { + let name = item.sig.ident.clone(); + let version = get_item_version(item)?.unwrap_or(1); + + match functions.entry(name.clone()) { + Entry::Vacant(entry) => { entry.insert(RuntimeInterfaceFunction::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); + } + } + } + + for function in functions.values() { + let mut next_expected = 1; + for (version, item) in function.versions.iter() { + if next_expected != *version { + return Err(Error::new( + item.span(), + format!("Unexpected version attribute: missing version '{}' for this function", next_expected), + )); + } + next_expected += 1; + } + } + + Ok(RuntimeInterface { items: functions }) +} \ No newline at end of file diff --git a/primitives/runtime-interface/src/lib.rs b/primitives/runtime-interface/src/lib.rs index 609f4f600b784ecdd8f30ca885256ad310f11e33..fd158d4b8aa9609df1206b2ce5a640e95b5e3092 100644 --- a/primitives/runtime-interface/src/lib.rs +++ b/primitives/runtime-interface/src/lib.rs @@ -129,11 +129,22 @@ pub use sp_std; /// /// A function that can be called from native/wasm. /// /// /// /// The implementation given to this function is only compiled on native. -/// fn call_some_complex_code(data: &[u8]) -> Vec { +/// 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. /// Vec::new() /// } +/// /// Call function, but different version. +/// /// +/// /// For new runtimes, only function with latest version is reachable. +/// /// But old version (above) is still accessible for old runtimes. +/// /// Default version is 1. +/// #[version(2)] +/// 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. +/// [17].to_vec() +/// } /// /// /// A function can take a `&self` or `&mut self` argument to get access to the /// /// `Externalities`. (The generated method does not require @@ -157,13 +168,15 @@ pub use sp_std; /// // on the visibility of the trait declaration. /// mod interface { /// trait Interface { -/// fn call_some_complex_code(data: &[u8]) -> Vec; -/// fn set_or_clear(&mut self, optional: Option>); +/// fn call_version_1(data: &[u8]) -> Vec; +/// fn call_version_2(data: &[u8]) -> Vec; +/// fn set_or_clear_version_1(&mut self, optional: Option>); /// } /// /// impl Interface for &mut dyn sp_externalities::Externalities { -/// fn call_some_complex_code(data: &[u8]) -> Vec { Vec::new() } -/// fn set_or_clear(&mut self, optional: Option>) { +/// fn call_version_1(data: &[u8]) -> Vec { Vec::new() } +/// fn call_version_2(data: &[u8]) -> Vec { [17].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), /// None => self.clear_storage(&[1, 2, 3, 4]), @@ -171,12 +184,25 @@ pub use sp_std; /// } /// } /// -/// pub fn call_some_complex_code(data: &[u8]) -> Vec { -/// <&mut dyn sp_externalities::Externalities as Interface>::call_some_complex_code(data) +/// pub fn call(data: &[u8]) -> Vec { +/// // only latest version is exposed +/// call_version_2(data) +/// } +/// +/// fn call_version_1(data: &[u8]) -> Vec { +/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_1(data) +/// } +/// +/// fn call_version_2(data: &[u8]) -> Vec { +/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_2(data) /// } /// /// pub fn set_or_clear(optional: Option>) { -/// sp_externalities::with_externalities(|mut ext| Interface::set_or_clear(&mut ext, optional)) +/// set_or_clear_version_1(optional) +/// } +/// +/// fn set_or_clear_version_1(optional: Option>) { +/// sp_externalities::with_externalities(|mut ext| Interface::set_or_clear_version_1(&mut ext, optional)) /// .expect("`set_or_clear` called outside of an Externalities-provided environment.") /// } /// @@ -205,7 +231,7 @@ pub use sp_std; /// /// `::FFIType`. /// /// /// /// `data` holds the pointer and the length to the `[u8]` slice. -/// pub fn ext_Interface_call_some_complex_code_version_1(data: u64) -> u64; +/// pub fn ext_Interface_call_version_1(data: u64) -> u64; /// /// `optional` holds the pointer and the length of the encoded value. /// pub fn ext_Interface_set_or_clear_version_1(optional: u64); /// } @@ -213,18 +239,18 @@ pub use sp_std; /// /// /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`). /// /// -/// /// This can be used to replace the implementation of the `call_some_complex_code` function. +/// /// This can be used to replace the implementation of the `call` function. /// /// Instead of calling into the host, the callee will automatically call the other /// /// implementation. /// /// /// /// To replace the implementation: /// /// -/// /// `host_call_some_complex_code.replace_implementation(some_other_impl)` -/// pub static host_call_some_complex_code: () = (); +/// /// `host_call.replace_implementation(some_other_impl)` +/// pub static host_call: () = (); /// pub static host_set_or_clear: () = (); /// -/// pub fn call_some_complex_code(data: &[u8]) -> Vec { -/// // This is the actual call: `host_call_some_complex_code.get()(data)` +/// pub fn call(data: &[u8]) -> Vec { +/// // This is the actual call: `host_call.get()(data)` /// // /// // But that does not work for several reasons in this example, so we just return an /// // empty vector. diff --git a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6f2d66bd77c4b2d672c54d3dc6f760830ccc570b --- /dev/null +++ b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "sp-runtime-interface-test-wasm-deprecated" +version = "2.0.0-dev" +authors = ["Parity Technologies "] +edition = "2018" +build = "build.rs" +license = "GPL-3.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +publish = false + +[dependencies] +sp-runtime-interface = { version = "2.0.0-alpha.5", default-features = false, path = "../" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../io" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../core" } + +[build-dependencies] +wasm-builder-runner = { version = "1.0.5", package = "substrate-wasm-builder-runner", path = "../../../utils/wasm-builder-runner" } + +[features] +default = [ "std" ] +std = [ "sp-runtime-interface/std", "sp-std/std", "sp-core/std", "sp-io/std" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/runtime-interface/test-wasm-deprecated/build.rs b/primitives/runtime-interface/test-wasm-deprecated/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..647b4768141d2203155bf5325f9ff87f7d9c2446 --- /dev/null +++ b/primitives/runtime-interface/test-wasm-deprecated/build.rs @@ -0,0 +1,26 @@ +// Copyright 2019-2020 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 wasm_builder_runner::WasmBuilder; + +fn main() { + WasmBuilder::new() + .with_current_project() + .with_wasm_builder_from_crates_or_path("1.0.9", "../../../utils/wasm-builder") + .export_heap_base() + .import_memory() + .build() +} diff --git a/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs b/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..29d28c75faafb9b4864479568c819e9bc3178477 --- /dev/null +++ b/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs @@ -0,0 +1,52 @@ +// Copyright 2020 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 . + +//! Tests for the runtime interface traits and proc macros. + +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_core::wasm_export_functions; +use sp_runtime_interface::runtime_interface; + +// Include the WASM binary +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +/// This function is not used, but we require it for the compiler to include `sp-io`. +/// `sp-io` is required for its panic and oom handler. +#[no_mangle] +pub fn import_sp_io() { + sp_io::misc::print_utf8(&[]); +} + +#[runtime_interface] +pub trait TestApi { + fn test_versionning(&self, _data: u32) -> bool { + // should not be called + unimplemented!() + } +} + +wasm_export_functions! { + fn test_versionning_works() { + // old api allows only 42 and 50 + assert!(test_api::test_versionning(42)); + assert!(test_api::test_versionning(50)); + + assert!(!test_api::test_versionning(142)); + assert!(!test_api::test_versionning(0)); + } +} \ No newline at end of file diff --git a/primitives/runtime-interface/test-wasm/Cargo.toml b/primitives/runtime-interface/test-wasm/Cargo.toml index b322e529d66735ea9e0418d0a3c2cf932c17eff3..4eb4f01c9f0c810ab42f9016e1f7db66147e1f65 100644 --- a/primitives/runtime-interface/test-wasm/Cargo.toml +++ b/primitives/runtime-interface/test-wasm/Cargo.toml @@ -10,10 +10,10 @@ repository = "https://github.com/paritytech/substrate/" publish = false [dependencies] -sp-runtime-interface = { version = "2.0.0-alpha.2", default-features = false, path = "../" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../io" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../core" } +sp-runtime-interface = { version = "2.0.0-alpha.5", default-features = false, path = "../" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../io" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../core" } [build-dependencies] wasm-builder-runner = { version = "1.0.5", package = "substrate-wasm-builder-runner", path = "../../../utils/wasm-builder-runner" } @@ -21,3 +21,6 @@ wasm-builder-runner = { version = "1.0.5", package = "substrate-wasm-builder-run [features] default = [ "std" ] std = [ "sp-runtime-interface/std", "sp-std/std", "sp-core/std", "sp-io/std" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/runtime-interface/test-wasm/src/lib.rs b/primitives/runtime-interface/test-wasm/src/lib.rs index ee7120b1b8cb59e17e2e524c86233afafda2c6f7..700c77854a8b3e003c56c1b25d2c0ba904550fd1 100644 --- a/primitives/runtime-interface/test-wasm/src/lib.rs +++ b/primitives/runtime-interface/test-wasm/src/lib.rs @@ -21,7 +21,7 @@ use sp_runtime_interface::runtime_interface; #[cfg(not(feature = "std"))] -use sp_std::{vec, vec::Vec, mem, convert::TryFrom}; +use sp_std::{prelude::*, mem, convert::TryFrom}; use sp_core::{sr25519::Public, wasm_export_functions}; @@ -103,6 +103,15 @@ pub trait TestApi { fn get_and_return_i128(val: i128) -> i128 { val } + + fn test_versionning(&self, data: u32) -> bool { + data == 42 || data == 50 + } + + #[version(2)] + fn test_versionning(&self, data: u32) -> bool { + data == 42 + } } /// This function is not used, but we require it for the compiler to include `sp-io`. @@ -231,4 +240,14 @@ wasm_export_functions! { } assert_eq!(0, len); } + + fn test_versionning_works() { + // we fix new api to accept only 42 as a proper input + // as opposed to sp-runtime-interface-test-wasm-deprecated::test_api::verify_input + // which accepted 42 and 50. + assert!(test_api::test_versionning(42)); + + assert!(!test_api::test_versionning(50)); + assert!(!test_api::test_versionning(102)); + } } diff --git a/primitives/runtime-interface/test/Cargo.toml b/primitives/runtime-interface/test/Cargo.toml index 53c05b68b3df285041dad7de43c4236c75566fb1..f3bee038c8204c612444c8c3d6e34a4e7f3e2126 100644 --- a/primitives/runtime-interface/test/Cargo.toml +++ b/primitives/runtime-interface/test/Cargo.toml @@ -9,9 +9,13 @@ homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" [dependencies] -sp-runtime-interface = { version = "2.0.0-alpha.2", path = "../" } -sc-executor = { version = "0.8.0-alpha.2", path = "../../../client/executor" } +sp-runtime-interface = { version = "2.0.0-alpha.5", path = "../" } +sc-executor = { version = "0.8.0-alpha.5", path = "../../../client/executor" } sp-runtime-interface-test-wasm = { version = "2.0.0-dev", path = "../test-wasm" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../../primitives/state-machine" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../runtime" } -sp-io = { version = "2.0.0-alpha.2", path = "../../io" } +sp-runtime-interface-test-wasm-deprecated = { version = "2.0.0-dev", path = "../test-wasm-deprecated" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../../primitives/state-machine" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../runtime" } +sp-io = { version = "2.0.0-alpha.5", path = "../../io" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/runtime-interface/test/src/lib.rs b/primitives/runtime-interface/test/src/lib.rs index 5236bf147e0f085e0d232648d8c480b62fa9f1bc..110eda980fd6947166768150e55c464c31c9ac80 100644 --- a/primitives/runtime-interface/test/src/lib.rs +++ b/primitives/runtime-interface/test/src/lib.rs @@ -20,13 +20,16 @@ #![cfg(test)] use sp_runtime_interface::*; + use sp_runtime_interface_test_wasm::{WASM_BINARY, test_api::HostFunctions}; +use sp_runtime_interface_test_wasm_deprecated::WASM_BINARY as WASM_BINARY_DEPRECATED; + use sp_wasm_interface::HostFunctions as HostFunctionsT; use sc_executor::CallInWasm; type TestExternalities = sp_state_machine::TestExternalities; -fn call_wasm_method(method: &str) -> TestExternalities { +fn call_wasm_method(binary: &[u8], method: &str) -> TestExternalities { let mut ext = TestExternalities::default(); let mut ext_ext = ext.ext(); let mut host_functions = HF::host_functions(); @@ -40,7 +43,7 @@ fn call_wasm_method(method: &str) -> TestExternalities { 8, ); executor.call_in_wasm( - &WASM_BINARY[..], + binary, None, method, &[], @@ -52,17 +55,17 @@ fn call_wasm_method(method: &str) -> TestExternalities { #[test] fn test_return_data() { - call_wasm_method::("test_return_data"); + call_wasm_method::(&WASM_BINARY[..], "test_return_data"); } #[test] fn test_return_option_data() { - call_wasm_method::("test_return_option_data"); + call_wasm_method::(&WASM_BINARY[..], "test_return_option_data"); } #[test] fn test_set_storage() { - let mut ext = call_wasm_method::("test_set_storage"); + let mut ext = call_wasm_method::(&WASM_BINARY[..], "test_set_storage"); let expected = "world"; assert_eq!(expected.as_bytes(), &ext.ext().storage("hello".as_bytes()).unwrap()[..]); @@ -70,22 +73,22 @@ fn test_set_storage() { #[test] fn test_return_value_into_mutable_reference() { - call_wasm_method::("test_return_value_into_mutable_reference"); + call_wasm_method::(&WASM_BINARY[..], "test_return_value_into_mutable_reference"); } #[test] fn test_get_and_return_array() { - call_wasm_method::("test_get_and_return_array"); + call_wasm_method::(&WASM_BINARY[..], "test_get_and_return_array"); } #[test] fn test_array_as_mutable_reference() { - call_wasm_method::("test_array_as_mutable_reference"); + call_wasm_method::(&WASM_BINARY[..], "test_array_as_mutable_reference"); } #[test] fn test_return_input_public_key() { - call_wasm_method::("test_return_input_public_key"); + call_wasm_method::(&WASM_BINARY[..], "test_return_input_public_key"); } #[test] @@ -93,7 +96,7 @@ fn test_return_input_public_key() { expected = "Instantiation: Export ext_test_api_return_input_version_1 not found" )] fn host_function_not_found() { - call_wasm_method::<()>("test_return_data"); + call_wasm_method::<()>(&WASM_BINARY[..], "test_return_data"); } #[test] @@ -104,30 +107,46 @@ fn host_function_not_found() { \\\"Invalid utf8 data provided\\\")) }\"" )] fn test_invalid_utf8_data_should_return_an_error() { - call_wasm_method::("test_invalid_utf8_data_should_return_an_error"); + call_wasm_method::(&WASM_BINARY[..], "test_invalid_utf8_data_should_return_an_error"); } #[test] fn test_overwrite_native_function_implementation() { - call_wasm_method::("test_overwrite_native_function_implementation"); + call_wasm_method::(&WASM_BINARY[..], "test_overwrite_native_function_implementation"); } #[test] fn test_u128_i128_as_parameter_and_return_value() { - call_wasm_method::("test_u128_i128_as_parameter_and_return_value"); + call_wasm_method::(&WASM_BINARY[..], "test_u128_i128_as_parameter_and_return_value"); } #[test] fn test_vec_return_value_memory_is_freed() { - call_wasm_method::("test_vec_return_value_memory_is_freed"); + call_wasm_method::(&WASM_BINARY[..], "test_vec_return_value_memory_is_freed"); } #[test] fn test_encoded_return_value_memory_is_freed() { - call_wasm_method::("test_encoded_return_value_memory_is_freed"); + call_wasm_method::(&WASM_BINARY[..], "test_encoded_return_value_memory_is_freed"); } #[test] fn test_array_return_value_memory_is_freed() { - call_wasm_method::("test_array_return_value_memory_is_freed"); + call_wasm_method::(&WASM_BINARY[..], "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[..], + "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[..], + "test_versionning_works", + ); } diff --git a/primitives/runtime-interface/tests/ui/no_duplicate_versions.rs b/primitives/runtime-interface/tests/ui/no_duplicate_versions.rs new file mode 100644 index 0000000000000000000000000000000000000000..948c327aa1a8b03c0b146031c013e07ca9b589f9 --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_duplicate_versions.rs @@ -0,0 +1,11 @@ +use sp_runtime_interface::runtime_interface; + +#[runtime_interface] +trait Test { + #[version(2)] + fn test() { } + #[version(2)] + fn test() { } +} + +fn main() {} diff --git a/primitives/runtime-interface/tests/ui/no_duplicate_versions.stderr b/primitives/runtime-interface/tests/ui/no_duplicate_versions.stderr new file mode 100644 index 0000000000000000000000000000000000000000..592dd9928c9687b875cc10d0d5ea15457c276c2d --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_duplicate_versions.stderr @@ -0,0 +1,11 @@ +error: Duplicated version attribute + --> $DIR/no_duplicate_versions.rs:7:2 + | +7 | #[version(2)] + | ^ + +error: Previous version with the same number defined here + --> $DIR/no_duplicate_versions.rs:5:2 + | +5 | #[version(2)] + | ^ diff --git a/primitives/runtime-interface/tests/ui/no_gaps_in_versions.rs b/primitives/runtime-interface/tests/ui/no_gaps_in_versions.rs new file mode 100644 index 0000000000000000000000000000000000000000..c468f48e37462015c7426f653e0012a528313a25 --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_gaps_in_versions.rs @@ -0,0 +1,17 @@ +use sp_runtime_interface::runtime_interface; + +#[runtime_interface] +trait Test { + #[version(1)] + fn test2() {} + #[version(2)] + fn test2() {} + #[version(3)] + fn test2() {} + + fn test() { } + #[version(3)] + fn test() { } +} + +fn main() {} diff --git a/primitives/runtime-interface/tests/ui/no_gaps_in_versions.stderr b/primitives/runtime-interface/tests/ui/no_gaps_in_versions.stderr new file mode 100644 index 0000000000000000000000000000000000000000..cdefcf60c56a8c4d27ab352e11cbd42d4198d241 --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_gaps_in_versions.stderr @@ -0,0 +1,5 @@ +error: Unexpected version attribute: missing version '2' for this function + --> $DIR/no_gaps_in_versions.rs:13:2 + | +13 | #[version(3)] + | ^ diff --git a/primitives/runtime-interface/tests/ui/no_generic_parameters.stderr b/primitives/runtime-interface/tests/ui/no_generic_parameters.stderr deleted file mode 100644 index c3e46655e5be790d126efa7a06efbbdd206866a9..0000000000000000000000000000000000000000 --- a/primitives/runtime-interface/tests/ui/no_generic_parameters.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Generic parameters not supported. - --> $DIR/no_generic_parameters.rs:5:10 - | -5 | fn test() {} - | ^ - -error: Generic parameters not supported. - --> $DIR/no_generic_parameters.rs:4:12 - | -4 | trait Test { - | ^ diff --git a/primitives/runtime-interface/tests/ui/no_generic_parameters_method.rs b/primitives/runtime-interface/tests/ui/no_generic_parameters_method.rs new file mode 100644 index 0000000000000000000000000000000000000000..407942eb5ed8899d83a698f7af4b34c6f1ca4ee5 --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_generic_parameters_method.rs @@ -0,0 +1,8 @@ +use sp_runtime_interface::runtime_interface; + +#[runtime_interface] +trait Test { + fn test() {} +} + +fn main() {} diff --git a/primitives/runtime-interface/tests/ui/no_generic_parameters_method.stderr b/primitives/runtime-interface/tests/ui/no_generic_parameters_method.stderr new file mode 100644 index 0000000000000000000000000000000000000000..8a549753ac9f9abf5f9cf2e1e3cd4e05d1f4b2c4 --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_generic_parameters_method.stderr @@ -0,0 +1,5 @@ +error: Generic parameters not supported. + --> $DIR/no_generic_parameters_method.rs:5:10 + | +5 | fn test() {} + | ^ diff --git a/primitives/runtime-interface/tests/ui/no_generic_parameters.rs b/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.rs similarity index 85% rename from primitives/runtime-interface/tests/ui/no_generic_parameters.rs rename to primitives/runtime-interface/tests/ui/no_generic_parameters_trait.rs index 17ddb00fab3b7a6c576518e452c6b4484ab8ed4d..35efac6761ca28acae520dca795e0995994b4f11 100644 --- a/primitives/runtime-interface/tests/ui/no_generic_parameters.rs +++ b/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.rs @@ -2,7 +2,7 @@ use sp_runtime_interface::runtime_interface; #[runtime_interface] trait Test { - fn test() {} + fn test() {} } fn main() {} diff --git a/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.stderr b/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.stderr new file mode 100644 index 0000000000000000000000000000000000000000..794e30bca767d3a3c0aed33713d7baca5d268f28 --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.stderr @@ -0,0 +1,5 @@ +error: Generic parameters not supported. + --> $DIR/no_generic_parameters_trait.rs:4:12 + | +4 | trait Test { + | ^ diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index fd40b9406aa23d9707f677af0fe48529b39aefa6..0e71e8becd61e4ea18a77e9ae4b5afcc031d731d 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-runtime" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,18 +12,18 @@ documentation = "https://docs.rs/sp-runtime" [dependencies] serde = { version = "1.0.101", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../core" } -sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../application-crypto" } -sp-arithmetic = { version = "2.0.0-alpha.2", default-features = false, path = "../arithmetic" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../io" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../core" } +sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../application-crypto" } +sp-arithmetic = { version = "2.0.0-alpha.5", default-features = false, path = "../arithmetic" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../io" } log = { version = "0.4.8", optional = true } paste = "0.1.6" rand = { version = "0.7.2", optional = true } impl-trait-for-tuples = "0.1.3" -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../inherents" } -parity-util-mem = { version = "0.5.2", default-features = false, features = ["primitive-types"] } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../inherents" } +parity-util-mem = { version = "0.6.0", default-features = false, features = ["primitive-types"] } hash256-std-hasher = { version = "0.15.2", default-features = false } [dev-dependencies] @@ -32,6 +32,7 @@ rand = "0.7.2" [features] bench = [] +runtime-benchmarks = [] default = ["std"] std = [ "sp-application-crypto/std", @@ -47,3 +48,6 @@ std = [ "parity-util-mem/std", "hash256-std-hasher/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/runtime/src/curve.rs b/primitives/runtime/src/curve.rs index e04ce77fb2cbb7dc1d5ed7d028e621799138153c..b00cbed6525a16cf6f1db35eed8f5b6e225437f3 100644 --- a/primitives/runtime/src/curve.rs +++ b/primitives/runtime/src/curve.rs @@ -16,7 +16,7 @@ //! Provides some utilities to define a piecewise linear function. -use crate::{Perbill, PerThing, traits::{AtLeast32Bit, SaturatedConversion}}; +use crate::{Perbill, traits::{AtLeast32Bit, SaturatedConversion}}; use core::ops::Sub; /// Piecewise Linear function in [0, 1] -> [0, 1]. diff --git a/primitives/runtime/src/generic/checked_extrinsic.rs b/primitives/runtime/src/generic/checked_extrinsic.rs index 25a8274354ae2aa60be8a97ff368aec2782abb2b..911a1131bb5fc8856cb8a5cc85cb01a03a121211 100644 --- a/primitives/runtime/src/generic/checked_extrinsic.rs +++ b/primitives/runtime/src/generic/checked_extrinsic.rs @@ -21,7 +21,7 @@ use crate::traits::{ self, Member, MaybeDisplay, SignedExtension, Dispatchable, }; use crate::traits::ValidateUnsigned; -use crate::transaction_validity::TransactionValidity; +use crate::transaction_validity::{TransactionValidity, TransactionSource}; /// Definition of something that the external world might want to say; its /// existence implies that it has been checked and is good, particularly with @@ -50,6 +50,9 @@ where fn validate>( &self, + // TODO [#5006;ToDr] should source be passed to `SignedExtension`s? + // Perhaps a change for 2.0 to avoid breaking too much APIs? + source: TransactionSource, info: Self::DispatchInfo, len: usize, ) -> TransactionValidity { @@ -57,7 +60,7 @@ where Extra::validate(extra, id, &self.function, info.clone(), len) } else { let valid = Extra::validate_unsigned(&self.function, info, len)?; - let unsigned_validation = U::validate_unsigned(&self.function)?; + let unsigned_validation = U::validate_unsigned(source, &self.function)?; Ok(valid.combine_with(unsigned_validation)) } } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 632deb13cbb45af6bd2271c2a5073b6ab05f0619..1a9aad6bdf1323923debf200376a8f8ea5844ba0 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -68,7 +68,10 @@ pub use sp_application_crypto::{RuntimeAppPublic, BoundToRuntimeAppPublic}; pub use sp_core::RuntimeDebug; /// Re-export top-level arithmetic stuff. -pub use sp_arithmetic::{Perquintill, Perbill, Permill, Percent, Rational128, Fixed64, PerThing}; +pub use sp_arithmetic::{ + Perquintill, Perbill, Permill, Percent, PerU16, Rational128, Fixed64, PerThing, + traits::SaturatedConversion, +}; /// Re-export 128 bit helpers. pub use sp_arithmetic::helpers_128bit; /// Re-export big_uint stuff. diff --git a/primitives/runtime/src/testing.rs b/primitives/runtime/src/testing.rs index 24390700542112b7083b17025e01ec167e43a995..b3139828c1d11865e9e9db95d7203dda4a1d1fd0 100644 --- a/primitives/runtime/src/testing.rs +++ b/primitives/runtime/src/testing.rs @@ -27,7 +27,7 @@ use crate::traits::ValidateUnsigned; use crate::{generic, KeyTypeId, ApplyExtrinsicResult}; pub use sp_core::{H256, sr25519}; use sp_core::{crypto::{CryptoType, Dummy, key_types, Public}, U256}; -use crate::transaction_validity::{TransactionValidity, TransactionValidityError}; +use crate::transaction_validity::{TransactionValidity, TransactionValidityError, TransactionSource}; /// Authority Id #[derive(Default, PartialEq, Eq, Clone, Encode, Decode, Debug, Hash, Serialize, Deserialize, PartialOrd, Ord)] @@ -357,6 +357,7 @@ impl Applyable for TestXt where /// Checks to see if this is a valid *transaction*. It returns information on it if so. fn validate>( &self, + _source: TransactionSource, _info: Self::DispatchInfo, _len: usize, ) -> TransactionValidity { diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 39e015505b20b3f5aecb7789c1144466e8ac42d3..6bd4b263c39e3becfd1dd50c1cc3d5cbf2f3ffab 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -28,7 +28,8 @@ use serde::{Serialize, Deserialize, de::DeserializeOwned}; use sp_core::{self, Hasher, TypeId, RuntimeDebug}; use crate::codec::{Codec, Encode, Decode}; use crate::transaction_validity::{ - ValidTransaction, TransactionValidity, TransactionValidityError, UnknownTransaction, + ValidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, + UnknownTransaction, }; use crate::generic::{Digest, DigestItem}; pub use sp_arithmetic::traits::{ @@ -156,6 +157,12 @@ pub trait EnsureOrigin { } /// Perform the origin check. fn try_origin(o: OuterOrigin) -> result::Result; + + /// Returns an outer origin capable of passing `try_origin` check. + /// + /// ** Should be used for benchmarking only!!! ** + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> OuterOrigin; } /// An error that indicates that a lookup failed. @@ -329,51 +336,6 @@ impl > SimpleBitOps for T {} -/// The block finalization trait. Implementing this lets you express what should happen -/// for your module when the block is ending. -#[impl_for_tuples(30)] -pub trait OnFinalize { - /// The block is being finalized. Implement to have something happen. - fn on_finalize(_n: BlockNumber) {} -} - -/// The block initialization trait. Implementing this lets you express what should happen -/// for your module when the block is beginning (right before the first extrinsic is executed). -#[impl_for_tuples(30)] -pub trait OnInitialize { - /// The block is being initialized. Implement to have something happen. - fn on_initialize(_n: BlockNumber) {} -} - -/// The runtime upgrade trait. Implementing this lets you express what should happen -/// when the runtime upgrades, and changes may need to occur to your module. -#[impl_for_tuples(30)] -pub trait OnRuntimeUpgrade { - /// Perform a module upgrade. - fn on_runtime_upgrade() {} -} - -/// Off-chain computation trait. -/// -/// Implementing this trait on a module allows you to perform long-running tasks -/// that make (by default) validators generate transactions that feed results -/// of those long-running computations back on chain. -/// -/// NOTE: This function runs off-chain, so it can access the block state, -/// 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_for_tuples(30)] -pub trait OffchainWorker { - /// This function is being called after every block import (when fully synced). - /// - /// Implement this and use any of the `Offchain` `sp_io` set of APIs - /// to perform off-chain computations, calls and submit transactions - /// with results to trigger any on-chain changes. - /// Any state alterations are lost and are not persisted. - fn offchain_worker(_n: BlockNumber) {} -} - /// Abstraction around hashing // Stupid bug in the Rust compiler believes derived // traits must be fulfilled by all type parameters. @@ -896,6 +858,7 @@ pub trait Applyable: Sized + Send + Sync { /// Checks to see if this is a valid *transaction*. It returns information on it if so. fn validate>( &self, + source: TransactionSource, info: Self::DispatchInfo, len: usize, ) -> TransactionValidity; @@ -942,7 +905,7 @@ pub trait ValidateUnsigned { /// /// Changes made to storage WILL be persisted if the call returns `Ok`. fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { - Self::validate_unsigned(call) + Self::validate_unsigned(TransactionSource::InBlock, call) .map(|_| ()) .map_err(Into::into) } @@ -953,7 +916,7 @@ pub trait ValidateUnsigned { /// whether the transaction would panic if it were included or not. /// /// Changes made to storage should be discarded by caller. - fn validate_unsigned(call: &Self::Call) -> TransactionValidity; + fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity; } /// Opaque data type that may be destructured into a series of raw byte slices (which represent @@ -966,7 +929,7 @@ pub trait OpaqueKeys: Clone { fn key_ids() -> &'static [crate::KeyTypeId]; /// Get the raw bytes of key with key-type ID `i`. fn get_raw(&self, i: super::KeyTypeId) -> &[u8]; - /// Get the decoded key with index `i`. + /// Get the decoded key with key-type ID `i`. fn get(&self, i: super::KeyTypeId) -> Option { T::decode(&mut self.get_raw(i)).ok() } @@ -1293,6 +1256,16 @@ impl Printable for &str { } } +impl Printable for bool { + fn print(&self) { + if *self { + "true".print() + } else { + "false".print() + } + } +} + #[impl_for_tuples(1, 12)] impl Printable for Tuple { fn print(&self) { diff --git a/primitives/runtime/src/transaction_validity.rs b/primitives/runtime/src/transaction_validity.rs index 6dfb80b6d7e4568b0bb0edabe51592e0506261c9..78f724b4d22aaef3530b6d9908731b3f401c64f2 100644 --- a/primitives/runtime/src/transaction_validity.rs +++ b/primitives/runtime/src/transaction_validity.rs @@ -161,6 +161,35 @@ impl Into for UnknownTransaction { } } +/// The source of the transaction. +/// +/// 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)] +pub enum TransactionSource { + /// Transaction is already included in block. + /// + /// This means that we can't really tell where the transaction is coming from, + /// since it's already in the received block. Note that the custom validation logic + /// using either `Local` or `External` should most likely just allow `InBlock` + /// transactions as well. + InBlock, + + /// Transaction is coming from a local source. + /// + /// This means that the transaction was produced internally by the node + /// (for instance an Off-Chain Worker, or an Off-Chain Call), as opposed + /// to being received over the network. + Local, + + /// Transaction has been received externally. + /// + /// This means the transaction has been received from (usually) "untrusted" source, + /// for instance received over the network or RPC. + External, +} + /// Information concerning a valid transaction. #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] pub struct ValidTransaction { diff --git a/primitives/sandbox/Cargo.toml b/primitives/sandbox/Cargo.toml index 060801a29e41e28df705a58353aca85baefc19ac..5c1595027f86fd129ebec97ffebe63b2b1263ca7 100755 --- a/primitives/sandbox/Cargo.toml +++ b/primitives/sandbox/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-sandbox" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,11 +10,11 @@ description = "This crate provides means to instantiate and execute wasm modules [dependencies] wasmi = { version = "0.6.2", optional = true } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../io" } -sp-wasm-interface = { version = "2.0.0-alpha.2", default-features = false, path = "../wasm-interface" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../io" } +sp-wasm-interface = { version = "2.0.0-alpha.5", default-features = false, path = "../wasm-interface" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } [dev-dependencies] wabt = "0.9.2" @@ -31,3 +31,6 @@ std = [ "sp-wasm-interface/std", ] strict = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/serializer/Cargo.toml b/primitives/serializer/Cargo.toml index 39f78c078b0caa8ee28a079155910005fcbf3916..75263321b805c85793e09f90ec0616ab1a6c4203 100644 --- a/primitives/serializer/Cargo.toml +++ b/primitives/serializer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-serializer" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,3 +12,6 @@ documentation = "https://docs.rs/sp-serializer" [dependencies] serde = "1.0.101" serde_json = "1.0.41" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/session/Cargo.toml b/primitives/session/Cargo.toml index d01f7ee440cf12e01a9cbca40c03d9082c34d073..ffe1bc327f7ffd9edb5f3c003abeb0f4d66077f3 100644 --- a/primitives/session/Cargo.toml +++ b/primitives/session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-session" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,11 +9,14 @@ repository = "https://github.com/paritytech/substrate/" description = "Primitives for sessions" [dependencies] -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../api" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../core" } -sp-runtime = { version = "2.0.0-alpha.2", optional = true, path = "../runtime" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../api" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../core" } +sp-runtime = { version = "2.0.0-alpha.5", optional = true, path = "../runtime" } [features] default = [ "std" ] std = [ "sp-api/std", "sp-std/std", "sp-runtime", "sp-core/std" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 2f85b8251b0ae4035aa98cceeaf56a6737c7cf21..60bf3f759e8dfcf0f3d2efe6bec7a3d231d291b8 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-staking" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,9 +9,9 @@ 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." [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } [features] default = ["std"] @@ -20,3 +20,6 @@ std = [ "sp-runtime/std", "sp-std/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/staking/src/offence.rs b/primitives/staking/src/offence.rs index 06e73f018b7656a691801b93b2178631cacd5ddd..584f3a75ea3abc2befd80821dba2f6b7349b2c5d 100644 --- a/primitives/staking/src/offence.rs +++ b/primitives/staking/src/offence.rs @@ -142,11 +142,20 @@ pub trait OnOffenceHandler { /// Zero is a valid value for a fraction. /// /// The `session` parameter is the session index of the offence. + /// + /// 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, - ); + ) -> Result<(), ()>; + + /// Can an offence be reported now or not. This is an method to short-circuit a call into + /// `on_offence`. Ideally, a correct implementation should return `false` if `on_offence` will + /// return `Err`. Nonetheless, this is up to the implementation and this trait cannot guarantee + /// it. + fn can_report() -> bool; } impl OnOffenceHandler for () { @@ -154,7 +163,9 @@ impl OnOffenceHandler for () { _offenders: &[OffenceDetails], _slash_fraction: &[Perbill], _session: SessionIndex, - ) {} + ) -> Result<(), ()> { Ok(()) } + + fn can_report() -> bool { true } } /// A details about an offending authority for a particular kind of offence. diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index 16f921c3ac137171d7a90f7820b8c41dcc382457..548f3f43086e928986dd5c546f105e86e2ebf0c1 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-state-machine" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Substrate State Machine" edition = "2018" @@ -15,17 +15,20 @@ parking_lot = "0.10.0" hash-db = "0.15.2" trie-db = "0.20.0" trie-root = "0.16.0" -sp-trie = { version = "2.0.0-alpha.2", path = "../trie" } -sp-core = { version = "2.0.0-alpha.2", path = "../core" } -sp-panic-handler = { version = "2.0.0-alpha.2", path = "../panic-handler" } -codec = { package = "parity-scale-codec", version = "1.2.0" } +sp-trie = { version = "2.0.0-alpha.5", path = "../trie" } +sp-core = { version = "2.0.0-alpha.5", path = "../core" } +sp-panic-handler = { version = "2.0.0-alpha.5", path = "../panic-handler" } +codec = { package = "parity-scale-codec", version = "1.3.0" } num-traits = "0.2.8" rand = "0.7.2" -sp-externalities = { version = "0.8.0-alpha.2", path = "../externalities" } +sp-externalities = { version = "0.8.0-alpha.5", path = "../externalities" } [dev-dependencies] hex-literal = "0.2.1" -sp-runtime = { version = "2.0.0-alpha.2", path = "../runtime" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../runtime" } [features] default = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/std/Cargo.toml b/primitives/std/Cargo.toml index 38a0c713c0563d88758ce4ace6e9eb790fc0369e..58ff78f2bbb510adc98a7de88415503cf71b0061 100644 --- a/primitives/std/Cargo.toml +++ b/primitives/std/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-std" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -13,3 +13,6 @@ documentation = "https://docs.rs/sp-std" [features] default = ["std"] std = [] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/storage/Cargo.toml b/primitives/storage/Cargo.toml index 7e434cdd898212aed62582e9e8dbcef92a16bd45..e4e842848dd0fe002e613099352b9f731bfeeeba 100644 --- a/primitives/storage/Cargo.toml +++ b/primitives/storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-storage" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" description = "Storage related primitives" @@ -10,11 +10,14 @@ repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sp-storage/" [dependencies] -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } serde = { version = "1.0.101", optional = true, features = ["derive"] } impl-serde = { version = "0.2.3", optional = true } -sp-debug-derive = { version = "2.0.0-alpha.2", path = "../debug-derive" } +sp-debug-derive = { version = "2.0.0-alpha.5", path = "../debug-derive" } [features] default = [ "std" ] std = [ "sp-std/std", "serde", "impl-serde" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/test-primitives/Cargo.toml b/primitives/test-primitives/Cargo.toml index 8788876769c1b08d346fef7afbf359447c3e440b..b8cb583835d89b4c25c0fa1583c36c8f66489729 100644 --- a/primitives/test-primitives/Cargo.toml +++ b/primitives/test-primitives/Cargo.toml @@ -9,12 +9,12 @@ repository = "https://github.com/paritytech/substrate/" publish = false [dependencies] -sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../application-crypto" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../core" } +sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../application-crypto" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../core" } serde = { version = "1.0.101", optional = true, features = ["derive"] } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } -parity-util-mem = { version = "0.5.2", default-features = false, features = ["primitive-types"] } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } +parity-util-mem = { version = "0.6.0", default-features = false, features = ["primitive-types"] } [features] default = [ @@ -24,3 +24,6 @@ std = [ "sp-application-crypto/std", "serde", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/timestamp/Cargo.toml b/primitives/timestamp/Cargo.toml index 1fc40a113f90378078398c633f508a9871c843dc..4a0851ccb19a843da2537b15d31843b0100e8cb3 100644 --- a/primitives/timestamp/Cargo.toml +++ b/primitives/timestamp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-timestamp" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,11 +9,11 @@ repository = "https://github.com/paritytech/substrate/" description = "Substrate core types and inherents for timestamps." [dependencies] -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../api" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../inherents" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../api" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../inherents" } impl-trait-for-tuples = "0.1.3" wasm-timer = "0.2" @@ -26,3 +26,6 @@ std = [ "codec/std", "sp-inherents/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/timestamp/src/lib.rs b/primitives/timestamp/src/lib.rs index 979b98b4952c92eaf3ad9b7f73d47807ada1d547..f1fd06a44a59f95e62d701f9b46fc4e4d0b9ce7e 100644 --- a/primitives/timestamp/src/lib.rs +++ b/primitives/timestamp/src/lib.rs @@ -77,6 +77,7 @@ impl TimestampInherentData for InherentData { } } +/// Provide duration since unix epoch in millisecond for timestamp inherent. #[cfg(feature = "std")] pub struct InherentDataProvider; diff --git a/primitives/transaction-pool/Cargo.toml b/primitives/transaction-pool/Cargo.toml index 69ec59cf5ef92015bde57140311f6d71f716c6fe..003212613ef3e57c9ff6cc9ea1455269651600d3 100644 --- a/primitives/transaction-pool/Cargo.toml +++ b/primitives/transaction-pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-transaction-pool" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -11,13 +11,13 @@ documentation = "https://docs.rs/sp-transaction-pool" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", optional = true } +codec = { package = "parity-scale-codec", version = "1.3.0", optional = true } derive_more = { version = "0.99.2", optional = true } futures = { version = "0.3.1", optional = true } log = { version = "0.4.8", optional = true } serde = { version = "1.0.101", features = ["derive"], optional = true} -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../api" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../api" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } [features] default = [ "std" ] @@ -30,3 +30,6 @@ std = [ "sp-api/std", "sp-runtime/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/transaction-pool/src/lib.rs b/primitives/transaction-pool/src/lib.rs index 9abbcfbdf28aaf4a066f8e28fc4fb174d129939c..e4498bd0248801dbe7b2f57cb5917003d5932c62 100644 --- a/primitives/transaction-pool/src/lib.rs +++ b/primitives/transaction-pool/src/lib.rs @@ -29,5 +29,5 @@ mod pool; pub use pool::*; pub use sp_runtime::transaction_validity::{ - TransactionLongevity, TransactionPriority, TransactionTag, + TransactionLongevity, TransactionPriority, TransactionTag, TransactionSource, }; diff --git a/primitives/transaction-pool/src/pool.rs b/primitives/transaction-pool/src/pool.rs index 89f327a523b5e5a8ad1c7fee3e9ef0394fb812e2..2c9d0e33e8b521f01e233bf82321158f8e3d7bac 100644 --- a/primitives/transaction-pool/src/pool.rs +++ b/primitives/transaction-pool/src/pool.rs @@ -29,9 +29,9 @@ use futures::{ use serde::{Deserialize, Serialize}; use sp_runtime::{ generic::BlockId, - traits::{Block as BlockT, Member}, + traits::{Block as BlockT, Member, NumberFor}, transaction_validity::{ - TransactionLongevity, TransactionPriority, TransactionTag, + TransactionLongevity, TransactionPriority, TransactionTag, TransactionSource, }, }; @@ -192,6 +192,7 @@ pub trait TransactionPool: Send + Sync { fn submit_at( &self, at: &BlockId, + source: TransactionSource, xts: Vec>, ) -> PoolFuture, Self::Error>>, Self::Error>; @@ -199,6 +200,7 @@ pub trait TransactionPool: Send + Sync { fn submit_one( &self, at: &BlockId, + source: TransactionSource, xt: TransactionFor, ) -> PoolFuture, Self::Error>; @@ -206,12 +208,20 @@ pub trait TransactionPool: Send + Sync { fn submit_and_watch( &self, at: &BlockId, + source: TransactionSource, xt: TransactionFor, ) -> PoolFuture>, Self::Error>; // *** Block production / Networking - /// Get an iterator for ready transactions ordered by priority - fn ready(&self) -> Box>>; + /// Get an iterator for ready transactions ordered by priority. + /// + /// Guarantees to return only when transaction pool got updated at `at` block. + /// Guarantees to return immediately when `None` is passed. + fn ready_at(&self, at: NumberFor) + -> Pin> + Send>> + Send>>; + + /// Get an iterator for ready transactions ordered by priority. + fn ready(&self) -> Box> + Send>; // *** Block production /// Remove transactions identified by given hashes (and dependent transactions) from the pool. @@ -292,7 +302,9 @@ impl OffchainSubmitTransaction for TPool { extrinsic ); - let result = futures::executor::block_on(self.submit_one(&at, extrinsic)); + let result = futures::executor::block_on(self.submit_one( + &at, TransactionSource::Local, extrinsic, + )); result.map(|_| ()) .map_err(|e| log::warn!( diff --git a/primitives/transaction-pool/src/runtime_api.rs b/primitives/transaction-pool/src/runtime_api.rs index e30ce7c8289da7b00e33dac71e592c496a3dafb7..fa2e51653b284fcde5ca6362017c4ec813c48a0a 100644 --- a/primitives/transaction-pool/src/runtime_api.rs +++ b/primitives/transaction-pool/src/runtime_api.rs @@ -16,13 +16,27 @@ //! Tagged Transaction Queue Runtime API. -use sp_runtime::transaction_validity::TransactionValidity; +use sp_runtime::transaction_validity::{TransactionValidity, TransactionSource}; use sp_runtime::traits::Block as BlockT; sp_api::decl_runtime_apis! { /// The `TaggedTransactionQueue` api trait for interfering with the transaction queue. + #[api_version(2)] pub trait TaggedTransactionQueue { - /// Validate the given transaction. + /// Validate the transaction. + #[changed_in(2)] fn validate_transaction(tx: ::Extrinsic) -> TransactionValidity; + + /// Validate the transaction. + /// + /// This method is invoked by the transaction pool to learn details about given transaction. + /// The implementation should make sure to verify the correctness of the transaction + /// against current state. + /// Note that this call may be performed by the pool multiple times and transactions + /// might be verified in any possible order. + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + ) -> TransactionValidity; } } diff --git a/primitives/trie/Cargo.toml b/primitives/trie/Cargo.toml index ff3c04b54193f0778bb5d503e8b3544da5054ebc..3876f4652624be83193661e83ea74f85c108920a 100644 --- a/primitives/trie/Cargo.toml +++ b/primitives/trie/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-trie" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] description = "Patricia trie stuff using a parity-scale-codec node format" repository = "https://github.com/paritytech/substrate/" @@ -14,20 +14,20 @@ name = "bench" harness = false [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } hash-db = { version = "0.15.2", default-features = false } trie-db = { version = "0.20.0", default-features = false } trie-root = { version = "0.16.0", default-features = false } -memory-db = { version = "0.19.0", default-features = false } -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../core" } +memory-db = { version = "0.20.0", default-features = false } +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../core" } [dev-dependencies] -trie-bench = "0.20.0" +trie-bench = "0.21.0" trie-standardmap = "0.15.2" criterion = "0.2.11" hex-literal = "0.2.1" -sp-runtime = { version = "2.0.0-alpha.2", path = "../runtime" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../runtime" } [features] default = ["std"] @@ -40,3 +40,6 @@ std = [ "trie-root/std", "sp-core/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/version/Cargo.toml b/primitives/version/Cargo.toml index edaf940465bd679cf94e9c319bc681d3ee680516..726d064642fb355a9d037ac840c44a5708db2850 100644 --- a/primitives/version/Cargo.toml +++ b/primitives/version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-version" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -13,9 +13,9 @@ documentation = "https://docs.rs/sp-version" [dependencies] impl-serde = { version = "0.2.3", optional = true } serde = { version = "1.0.101", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../std" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../runtime" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../runtime" } [features] default = ["std"] @@ -26,3 +26,6 @@ std = [ "sp-std/std", "sp-runtime/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index bbe53df258271b74c4fd36a035368c915cc28768..4a35d5b5180ff07529762e003370e7d2f21eef2b 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-wasm-interface" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -12,9 +12,12 @@ documentation = "https://docs.rs/sp-wasm-interface" [dependencies] wasmi = { version = "0.6.2", optional = true } impl-trait-for-tuples = "0.1.2" -sp-std = { version = "2.0.0-alpha.2", path = "../std", default-features = false } -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +sp-std = { version = "2.0.0-alpha.5", path = "../std", default-features = false } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } [features] default = [ "std" ] std = [ "wasmi", "sp-std/std", "codec/std" ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index d3477b94a6f95e608b6df5c75ca03845af386825..b8c9f9bd606059b73adb080f68c78dda3e8026a5 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "substrate-test-utils" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/test-utils/client/Cargo.toml b/test-utils/client/Cargo.toml index 77a614e5fecca7ed6686da5e846dfe975ab4e64b..ec87e7cd168f05585f9f70a25793fc4840af5017 100644 --- a/test-utils/client/Cargo.toml +++ b/test-utils/client/Cargo.toml @@ -9,16 +9,19 @@ repository = "https://github.com/paritytech/substrate/" publish = false [dependencies] -sc-client-api = { version = "2.0.0-alpha.2", path = "../../client/api" } -sc-client = { version = "0.8.0-alpha.2", path = "../../client/" } -sc-client-db = { version = "0.8.0-alpha.2", features = ["test-helpers"], path = "../../client/db" } -sp-consensus = { version = "0.8.0-alpha.2", path = "../../primitives/consensus/common" } -sc-executor = { version = "0.8.0-alpha.2", path = "../../client/executor" } -futures = "0.3.1" +sc-client-api = { version = "2.0.0-alpha.5", path = "../../client/api" } +sc-client = { version = "0.8.0-alpha.5", path = "../../client/" } +sc-client-db = { version = "0.8.0-alpha.5", features = ["test-helpers"], path = "../../client/db" } +sp-consensus = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/common" } +sc-executor = { version = "0.8.0-alpha.5", path = "../../client/executor" } +futures = "0.3.4" hash-db = "0.15.2" -sp-keyring = { version = "2.0.0-alpha.2", path = "../../primitives/keyring" } -codec = { package = "parity-scale-codec", version = "1.2.0" } -sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../primitives/runtime" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../primitives/blockchain" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../primitives/state-machine" } +sp-keyring = { version = "2.0.0-alpha.5", path = "../../primitives/keyring" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../primitives/blockchain" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../primitives/state-machine" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 629656f10fbe27c5b6ad2370e860690c79ea40e8..be22747ea69166d91f83102e79171c18c8e5441a 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -10,43 +10,43 @@ repository = "https://github.com/paritytech/substrate/" publish = false [dependencies] -sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/application-crypto" } -sp-consensus-aura = { version = "0.8.0-alpha.2", default-features = false, path = "../../primitives/consensus/aura" } -sp-consensus-babe = { version = "0.8.0-alpha.2", default-features = false, path = "../../primitives/consensus/babe" } -sp-block-builder = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/block-builder" } +sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/application-crypto" } +sp-consensus-aura = { version = "0.8.0-alpha.5", default-features = false, path = "../../primitives/consensus/aura" } +sp-consensus-babe = { version = "0.8.0-alpha.5", default-features = false, path = "../../primitives/consensus/babe" } +sp-block-builder = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/block-builder" } cfg-if = "0.1.10" -codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -frame-executive = { version = "2.0.0-alpha.2", default-features = false, path = "../../frame/executive" } -sp-inherents = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/inherents" } -sp-keyring = { version = "2.0.0-alpha.2", optional = true, path = "../../primitives/keyring" } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +frame-executive = { version = "2.0.0-alpha.5", default-features = false, path = "../../frame/executive" } +sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" } +sp-keyring = { version = "2.0.0-alpha.5", optional = true, path = "../../primitives/keyring" } log = { version = "0.4.8", optional = true } -memory-db = { version = "0.19.0", default-features = false } -sp-offchain = { path = "../../primitives/offchain", default-features = false, version = "2.0.0-alpha.2"} -sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/core" } -sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/std" } -sp-runtime-interface = { path = "../../primitives/runtime-interface", default-features = false, version = "2.0.0-alpha.2"} -sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/io" } -frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../../frame/support" } -sp-version = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/version" } +memory-db = { version = "0.20.0", default-features = false } +sp-offchain = { path = "../../primitives/offchain", default-features = false, version = "2.0.0-alpha.5"} +sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } +sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } +sp-runtime-interface = { path = "../../primitives/runtime-interface", default-features = false, version = "2.0.0-alpha.5"} +sp-io = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/io" } +frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../../frame/support" } +sp-version = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/version" } serde = { version = "1.0.101", optional = true, features = ["derive"] } -sp-session = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/session" } -sp-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/api" } -sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/runtime" } -pallet-babe = { version = "2.0.0-alpha.2", default-features = false, path = "../../frame/babe" } -frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../../frame/system" } -frame-system-rpc-runtime-api = { version = "2.0.0-alpha.2", default-features = false, path = "../../frame/system/rpc/runtime-api" } -pallet-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../../frame/timestamp" } -sc-client = { version = "0.8.0-alpha.2", optional = true, path = "../../client" } -sp-trie = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/trie" } -sp-transaction-pool = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/transaction-pool" } +sp-session = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/session" } +sp-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/api" } +sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +pallet-babe = { version = "2.0.0-alpha.5", default-features = false, path = "../../frame/babe" } +frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../../frame/system" } +frame-system-rpc-runtime-api = { version = "2.0.0-alpha.5", default-features = false, path = "../../frame/system/rpc/runtime-api" } +pallet-timestamp = { version = "2.0.0-alpha.5", default-features = false, path = "../../frame/timestamp" } +sc-client = { version = "0.8.0-alpha.5", optional = true, path = "../../client" } +sp-trie = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/trie" } +sp-transaction-pool = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/transaction-pool" } trie-db = { version = "0.20.0", default-features = false } -parity-util-mem = { version = "0.5.2", default-features = false, features = ["primitive-types"] } +parity-util-mem = { version = "0.6.0", default-features = false, features = ["primitive-types"] } [dev-dependencies] -sc-block-builder = { version = "0.8.0-alpha.2", path = "../../client/block-builder" } -sc-executor = { version = "0.8.0-alpha.2", path = "../../client/executor" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "../../client/block-builder" } +sc-executor = { version = "0.8.0-alpha.5", path = "../../client/executor" } substrate-test-runtime-client = { version = "2.0.0-dev", path = "./client" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../primitives/state-machine" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../primitives/state-machine" } [build-dependencies] wasm-builder-runner = { version = "1.0.5", package = "substrate-wasm-builder-runner", path = "../../utils/wasm-builder-runner" } @@ -87,3 +87,6 @@ std = [ "sp-transaction-pool/std", "trie-db/std", ] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/test-utils/runtime/client/Cargo.toml b/test-utils/runtime/client/Cargo.toml index 5f16e77860ba68083ce8c4e1ff6088cb78d156fc..4be45fe46659a2583df6c02b2c67a32f8bf64a18 100644 --- a/test-utils/runtime/client/Cargo.toml +++ b/test-utils/runtime/client/Cargo.toml @@ -9,14 +9,17 @@ repository = "https://github.com/paritytech/substrate/" publish = false [dependencies] -sc-block-builder = { version = "0.8.0-alpha.2", path = "../../../client/block-builder" } +sc-block-builder = { version = "0.8.0-alpha.5", path = "../../../client/block-builder" } substrate-test-client = { version = "2.0.0-dev", path = "../../client" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } substrate-test-runtime = { version = "2.0.0-dev", path = "../../runtime" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-api = { version = "2.0.0-alpha.2", path = "../../../primitives/api" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -codec = { package = "parity-scale-codec", version = "1.2.0" } -sc-client-api = { version = "2.0.0-alpha.2", path = "../../../client/api" } -sc-client = { version = "0.8.0-alpha.2", path = "../../../client/" } -futures = "0.3.1" +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../primitives/api" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +sc-client-api = { version = "2.0.0-alpha.5", path = "../../../client/api" } +sc-client = { version = "0.8.0-alpha.5", path = "../../../client/" } +futures = "0.3.4" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index bc5e0070ebe8f79670f7d684779de5c307dade1f..f3efb4bea7c763f89aa9e8e79a1770d2c73c3c97 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -36,6 +36,7 @@ use sp_runtime::{ ApplyExtrinsicResult, create_runtime_str, Perbill, impl_opaque_keys, transaction_validity::{ TransactionValidity, ValidTransaction, TransactionValidityError, InvalidTransaction, + TransactionSource, }, traits::{ BlindCheckable, BlakeTwo256, Block as BlockT, Extrinsic as ExtrinsicT, @@ -391,7 +392,7 @@ impl frame_system::Trait for Runtime { type Version = (); type ModuleToIndex = (); type AccountData = (); - type MigrateAccount = (); type OnNewAccount = (); + type OnNewAccount = (); type OnKilledAccount = (); } @@ -492,7 +493,10 @@ cfg_if! { } impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction(utx: ::Extrinsic) -> TransactionValidity { + fn validate_transaction( + _source: TransactionSource, + utx: ::Extrinsic, + ) -> TransactionValidity { if let Extrinsic::IncludeData(data) = utx { return Ok(ValidTransaction { priority: data.len() as u64, @@ -679,7 +683,10 @@ cfg_if! { } impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction(utx: ::Extrinsic) -> TransactionValidity { + fn validate_transaction( + _source: TransactionSource, + utx: ::Extrinsic, + ) -> TransactionValidity { if let Extrinsic::IncludeData(data) = utx { return Ok(ValidTransaction{ priority: data.len() as u64, diff --git a/test-utils/runtime/transaction-pool/Cargo.toml b/test-utils/runtime/transaction-pool/Cargo.toml index 3e22da468f1e39d914243d8aa3cdf44fdd98a658..52e2020dc8dd18dfc82de735e66b98b469bfefb9 100644 --- a/test-utils/runtime/transaction-pool/Cargo.toml +++ b/test-utils/runtime/transaction-pool/Cargo.toml @@ -11,10 +11,13 @@ publish = false [dependencies] substrate-test-runtime-client = { version = "2.0.0-dev", path = "../client" } parking_lot = "0.10.0" -codec = { package = "parity-scale-codec", version = "1.2.0" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../primitives/blockchain" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../../primitives/transaction-pool" } -sc-transaction-graph = { version = "2.0.0-alpha.2", path = "../../../client/transaction-pool/graph" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../primitives/blockchain" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../../primitives/transaction-pool" } +sc-transaction-graph = { version = "2.0.0-alpha.5", path = "../../../client/transaction-pool/graph" } futures = { version = "0.3.1", features = ["compat"] } derive_more = "0.99.2" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/test-utils/runtime/transaction-pool/src/lib.rs b/test-utils/runtime/transaction-pool/src/lib.rs index 8cd4e58954b73c931f89b7178fee92896ce21c5e..432c9e520d1b178c0dc2567a35be82873d9ee687 100644 --- a/test-utils/runtime/transaction-pool/src/lib.rs +++ b/test-utils/runtime/transaction-pool/src/lib.rs @@ -25,6 +25,7 @@ use sp_runtime::{ traits::{BlakeTwo256, Hash as HashT}, transaction_validity::{ TransactionValidity, ValidTransaction, TransactionValidityError, InvalidTransaction, + TransactionSource, }, }; use std::collections::{HashSet, HashMap}; @@ -180,6 +181,7 @@ impl sc_transaction_graph::ChainApi for TestApi { fn validate_transaction( &self, _at: &BlockId, + _source: TransactionSource, uxt: sc_transaction_graph::ExtrinsicFor, ) -> Self::ValidationFuture { self.validation_requests.write().push(uxt.clone()); diff --git a/utils/browser/Cargo.toml b/utils/browser/Cargo.toml index 205f42d61fb14fc9dc119584901e7ea17355fe0b..188f46bf197b831aaad001528befd4cc4b60f47c 100644 --- a/utils/browser/Cargo.toml +++ b/utils/browser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "substrate-browser-utils" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" authors = ["Parity Technologies "] description = "Utilities for creating a browser light-client." edition = "2018" @@ -18,11 +18,11 @@ console_log = "0.1.2" js-sys = "0.3.34" wasm-bindgen = "0.2.57" wasm-bindgen-futures = "0.4.7" -kvdb-web = "0.4" -sc-informant = { version = "0.8.0-alpha.2", path = "../../client/informant" } -sc-service = { version = "0.8.0-alpha.2", path = "../../client/service", default-features = false } -sc-network = { path = "../../client/network" , version = "0.8.0-alpha.2"} -sc-chain-spec = { path = "../../client/chain-spec" , version = "2.0.0-alpha.2"} +kvdb-web = "0.5" +sc-informant = { version = "0.8.0-alpha.5", path = "../../client/informant" } +sc-service = { version = "0.8.0-alpha.5", path = "../../client/service", default-features = false } +sc-network = { path = "../../client/network", version = "0.8.0-alpha.5"} +sc-chain-spec = { path = "../../client/chain-spec", version = "2.0.0-alpha.5"} # Imported just for the `no_cc` feature clear_on_drop = { version = "0.2.3", features = ["no_cc"] } @@ -31,3 +31,6 @@ rand6 = { package = "rand", version = "0.6", features = ["wasm-bindgen"] } rand = { version = "0.7", features = ["wasm-bindgen"] } futures-timer = { version = "3.0.1", features = ["wasm-bindgen"]} chrono = { version = "0.4", features = ["wasmbind"] } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/browser/src/lib.rs b/utils/browser/src/lib.rs index c9c159efe3a359effb1174ea2b4d0728239f525f..3a0162bd90abb5b0fdd8c09d500cafeae52dafd6 100644 --- a/utils/browser/src/lib.rs +++ b/utils/browser/src/lib.rs @@ -38,7 +38,7 @@ pub async fn browser_configuration(chain_spec: GenericChainSpec) -> Result> where G: RuntimeGenesis + 'static, - E: Extension + 'static, + E: Extension + 'static + Send, { let name = chain_spec.name().to_string(); diff --git a/utils/build-script-utils/Cargo.toml b/utils/build-script-utils/Cargo.toml index 5903d107fbbe138b123e4c27aafe3b7dfecbbadb..3fe10f6a825a82585396eccdd3d3c447edf9a9d4 100644 --- a/utils/build-script-utils/Cargo.toml +++ b/utils/build-script-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "substrate-build-script-utils" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,3 +9,6 @@ repository = "https://github.com/paritytech/substrate/" description = "Crate with utility functions for `build.rs` scripts." [dependencies] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/fork-tree/Cargo.toml b/utils/fork-tree/Cargo.toml index 8ade20cd7b7e29c44900be94ae8fe06bb0f8cf49..e46618feb8e5a9d8dc4aed8f100c5f6bfcea1323 100644 --- a/utils/fork-tree/Cargo.toml +++ b/utils/fork-tree/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fork-tree" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -10,4 +10,7 @@ description = "Utility library for managing tree-like ordered data with logic fo documentation = "https://docs.rs/fork-tree" [dependencies] -codec = { package = "parity-scale-codec", version = "1.2.0", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 67b217c287295d76fa5b6a5bb3604246ae10397f..f4b72187478d47c63575c01ad27d86500c160816 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-benchmarking-cli" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,14 +9,22 @@ repository = "https://github.com/paritytech/substrate/" description = "CLI for benchmarking FRAME" [dependencies] -frame-benchmarking = { version = "2.0.0-alpha.2", path = "../../../frame/benchmarking" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } -sc-service = { version = "0.8.0-alpha.2", default-features = false, path = "../../../client/service" } -sc-cli = { version = "0.8.0-alpha.2", path = "../../../client/cli" } -sc-client = { version = "0.8.0-alpha.2", path = "../../../client" } -sc-client-db = { version = "0.8.0-alpha.2", path = "../../../client/db" } -sc-executor = { version = "0.8.0-alpha.2", path = "../../../client/executor" } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.8.0-alpha.2", path = "../../../primitives/state-machine" } +frame-benchmarking = { version = "2.0.0-alpha.5", path = "../../../frame/benchmarking" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" } +sc-service = { version = "0.8.0-alpha.5", default-features = false, path = "../../../client/service" } +sc-cli = { version = "0.8.0-alpha.5", path = "../../../client/cli" } +sc-client = { version = "0.8.0-alpha.5", path = "../../../client" } +sc-client-db = { version = "0.8.0-alpha.5", path = "../../../client/db" } +sc-executor = { version = "0.8.0-alpha.5", path = "../../../client/executor" } +sp-externalities = { version = "0.8.0-alpha.5", path = "../../../primitives/externalities" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.8.0-alpha.5", path = "../../../primitives/state-machine" } structopt = "0.3.8" -codec = { version = "1.2.0", package = "parity-scale-codec" } +codec = { version = "1.3.0", package = "parity-scale-codec" } + +[features] +default = ["rocksdb"] +rocksdb = ["sc-client-db/kvdb-rocksdb"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index 79e7e689a6420eb1f38c05180e8cd5411c2ee0a6..f9851810bf6c93610292eb4b6b42be0aaf56f56f 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -22,17 +22,22 @@ use sc_client_db::BenchmarkingState; use sc_service::{Configuration, ChainSpec}; use sc_executor::{NativeExecutor, NativeExecutionDispatch}; use codec::{Encode, Decode}; -use frame_benchmarking::BenchmarkResults; -use sp_core::tasks; +use frame_benchmarking::{BenchmarkBatch, Analysis}; +use sp_core::{ + tasks, + traits::KeystoreExt, + testing::KeyStore, +}; +use sp_externalities::Extensions; /// The `benchmark` command used to benchmark FRAME Pallets. #[derive(Debug, structopt::StructOpt, Clone)] pub struct BenchmarkCmd { - /// Select a FRAME Pallet to benchmark. + /// Select a FRAME Pallet to benchmark, or `*` for all (in which case `extrinsic` must be `*`). #[structopt(short, long)] pub pallet: String, - /// Select an extrinsic to benchmark. + /// Select an extrinsic inside the pallet to benchmark, or `*` for all. #[structopt(short, long)] pub extrinsic: String, @@ -41,17 +46,29 @@ pub struct BenchmarkCmd { pub steps: Vec, /// Indicates lowest values for each of the component ranges. - #[structopt(long, use_delimiter = true)] + #[structopt(long = "low", use_delimiter = true)] pub lowest_range_values: Vec, /// Indicates highest values for each of the component ranges. - #[structopt(long, use_delimiter = true)] + #[structopt(long = "high", use_delimiter = true)] pub highest_range_values: Vec, /// Select how many repetitions of this benchmark should run. #[structopt(short, long, default_value = "1")] pub 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, + #[allow(missing_docs)] #[structopt(flatten)] pub shared_params: sc_cli::SharedParams, @@ -106,6 +123,9 @@ impl BenchmarkCmd { 2, // The runtime instances cache size. ); + let mut extensions = Extensions::default(); + extensions.register(KeystoreExt(KeyStore::new())); + let result = StateMachine::<_, _, NumberFor, _>::new( &state, None, @@ -120,43 +140,57 @@ impl BenchmarkCmd { self.steps.clone(), self.repeat, ).encode(), - Default::default(), + extensions, &sp_state_machine::backend::BackendRuntimeCode::new(&state).runtime_code()?, tasks::executor(), ) .execute(strategy.into()) .map_err(|e| format!("Error executing runtime benchmark: {:?}", e))?; - let results = , String> as Decode>::decode(&mut &result[..]) + let results = , String> as Decode>::decode(&mut &result[..]) .map_err(|e| format!("Failed to decode benchmark results: {:?}", e))?; match results { - Ok(results) => { + Ok(batches) => for batch in batches.into_iter() { // Print benchmark metadata println!( "Pallet: {:?}, Extrinsic: {:?}, Lowest values: {:?}, Highest values: {:?}, Steps: {:?}, Repeat: {:?}", - self.pallet, - self.extrinsic, + 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, ); - // Print the table header - results[0].0.iter().for_each(|param| print!("{:?},", param.0)); - - print!("extrinsic_time,storage_root_time\n"); - // Print the values - results.iter().for_each(|result| { - let parameters = &result.0; - parameters.iter().for_each(|param| print!("{:?},", param.1)); - // Print extrinsic time and storage root time - print!("{:?},{:?}\n", result.1, result.2); - }); - - eprintln!("Done."); - } + if self.raw_data { + // Print the table header + batch.results[0].0.iter().for_each(|param| print!("{:?},", param.0)); + + print!("extrinsic_time,storage_root_time\n"); + // Print the values + batch.results.iter().for_each(|result| { + let parameters = &result.0; + parameters.iter().for_each(|param| print!("{:?},", param.1)); + // Print extrinsic time and storage root time + print!("{:?},{:?}\n", result.1, result.2); + }); + + print!("\n"); + } + + // Conduct analysis. + if !self.no_median_slopes { + if let Some(analysis) = Analysis::median_slopes(&batch.results) { + println!("Median Slopes Analysis\n========\n{}", analysis); + } + } + if !self.no_min_squares { + if let Some(analysis) = Analysis::min_squares_iqr(&batch.results) { + println!("Min Squares Analysis\n========\n{}", analysis); + } + } + }, Err(error) => eprintln!("Error: {:?}", error), } diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index 162d25cf9bfcddebb1691d6ff54db1daac76a3fc..72884330d2e8224cc9959beb5c23bcd67166dea1 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "substrate-frame-rpc-support" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies ", "Andrew Dirksen "] edition = "2018" license = "GPL-3.0" @@ -10,14 +10,17 @@ description = "Substrate RPC for FRAME's support" [dependencies] futures = { version = "0.3.0", features = ["compat"] } -jsonrpc-client-transports = "14" +jsonrpc-client-transports = { version = "14.0.5", default-features = false, features = ["http"] } jsonrpc-core = "14" codec = { package = "parity-scale-codec", version = "1" } serde = "1" -frame-support = { version = "2.0.0-alpha.2", path = "../../../../frame/support" } -sp-storage = { version = "2.0.0-alpha.2", path = "../../../../primitives/storage" } -sc-rpc-api = { version = "0.8.0-alpha.2", path = "../../../../client/rpc-api" } +frame-support = { version = "2.0.0-alpha.5", path = "../../../../frame/support" } +sp-storage = { version = "2.0.0-alpha.5", path = "../../../../primitives/storage" } +sc-rpc-api = { version = "0.8.0-alpha.5", path = "../../../../client/rpc-api" } [dev-dependencies] -frame-system = { version = "2.0.0-alpha.2", path = "../../../../frame/system" } +frame-system = { version = "2.0.0-alpha.5", path = "../../../../frame/system" } tokio = "0.2" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index 8b1c62ccd70e2c2290ec29a56156d3d61aac9474..a9e775393dfb17702933d17399a80824872ba5ee 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "substrate-frame-rpc-system" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.5" authors = ["Parity Technologies "] edition = "2018" license = "GPL-3.0" @@ -9,22 +9,25 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME's system exposed over Substrate RPC" [dependencies] -sc-client = { version = "0.8.0-alpha.2", path = "../../../../client/" } -codec = { package = "parity-scale-codec", version = "1.2.0" } -futures = "0.3.1" +sc-client = { version = "0.8.0-alpha.5", path = "../../../../client/" } +codec = { package = "parity-scale-codec", version = "1.3.0" } +futures = "0.3.4" jsonrpc-core = "14.0.3" -jsonrpc-core-client = "14.0.3" +jsonrpc-core-client = "14.0.5" jsonrpc-derive = "14.0.3" log = "0.4.8" serde = { version = "1.0.101", features = ["derive"] } -sp-runtime = { version = "2.0.0-alpha.2", path = "../../../../primitives/runtime" } -sp-api = { version = "2.0.0-alpha.2", path = "../../../../primitives/api" } -frame-system-rpc-runtime-api = { version = "2.0.0-alpha.2", path = "../../../../frame/system/rpc/runtime-api" } -sp-core = { version = "2.0.0-alpha.2", path = "../../../../primitives/core" } -sp-blockchain = { version = "2.0.0-alpha.2", path = "../../../../primitives/blockchain" } -sp-transaction-pool = { version = "2.0.0-alpha.2", path = "../../../../primitives/transaction-pool" } +sp-runtime = { version = "2.0.0-alpha.5", path = "../../../../primitives/runtime" } +sp-api = { version = "2.0.0-alpha.5", path = "../../../../primitives/api" } +frame-system-rpc-runtime-api = { version = "2.0.0-alpha.5", path = "../../../../frame/system/rpc/runtime-api" } +sp-core = { version = "2.0.0-alpha.5", path = "../../../../primitives/core" } +sp-blockchain = { version = "2.0.0-alpha.5", path = "../../../../primitives/blockchain" } +sp-transaction-pool = { version = "2.0.0-alpha.5", path = "../../../../primitives/transaction-pool" } [dev-dependencies] substrate-test-runtime-client = { version = "2.0.0-dev", path = "../../../../test-utils/runtime/client" } env_logger = "0.7.0" -sc-transaction-pool = { version = "2.0.0-alpha.2", path = "../../../../client/transaction-pool" } +sc-transaction-pool = { version = "2.0.0-alpha.5", path = "../../../../client/transaction-pool" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/frame/rpc/system/src/lib.rs b/utils/frame/rpc/system/src/lib.rs index e6214c73d6ea1ab3f0a3b4ffc9cc3d05d4cdd660..c73ddfe93efa067d8188f9abb66c554c9865baad 100644 --- a/utils/frame/rpc/system/src/lib.rs +++ b/utils/frame/rpc/system/src/lib.rs @@ -239,6 +239,7 @@ mod tests { BasicPool::new(Default::default(), Arc::new(FullChainApi::new(client.clone()))).0 ); + let source = sp_runtime::transaction_validity::TransactionSource::External; let new_transaction = |nonce: u64| { let t = Transfer { from: AccountKeyring::Alice.into(), @@ -250,9 +251,9 @@ mod tests { }; // Populate the pool let ext0 = new_transaction(0); - block_on(pool.submit_one(&BlockId::number(0), ext0)).unwrap(); + block_on(pool.submit_one(&BlockId::number(0), source, ext0)).unwrap(); let ext1 = new_transaction(1); - block_on(pool.submit_one(&BlockId::number(0), ext1)).unwrap(); + block_on(pool.submit_one(&BlockId::number(0), source, ext1)).unwrap(); let accounts = FullSystem::new(client, pool); diff --git a/utils/prometheus/Cargo.toml b/utils/prometheus/Cargo.toml index bc7750a72083fa59249db011549c8d63b777db59..d2913c5e59808c4e8dde3460b9a77f850ece508f 100644 --- a/utils/prometheus/Cargo.toml +++ b/utils/prometheus/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Endpoint to expose Prometheus metrics" name = "substrate-prometheus-endpoint" -version = "0.8.0-alpha.3" +version = "0.8.0-alpha.5" license = "GPL-3.0" authors = ["Parity Technologies "] edition = "2018" @@ -18,3 +18,6 @@ derive_more = "0.99" async-std = { version = "1.0.1", features = ["unstable"] } hyper = { version = "0.13.1", default-features = false, features = ["stream"] } tokio = "0.2" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/wasm-builder-runner/Cargo.toml b/utils/wasm-builder-runner/Cargo.toml index 8a41fe98b2728efee0a0e71c946853f4adde7be2..77796ea8d9a8b15c21d7531fb772afc43c47ee6a 100644 --- a/utils/wasm-builder-runner/Cargo.toml +++ b/utils/wasm-builder-runner/Cargo.toml @@ -10,3 +10,6 @@ license = "GPL-3.0" homepage = "https://substrate.dev" [dependencies] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/wasm-builder/Cargo.toml b/utils/wasm-builder/Cargo.toml index 1aac8913939b1253e66ad50e53dd18d3d1177cd8..ed953cca577ddf1deaec0f8600b7d473293ab24c 100644 --- a/utils/wasm-builder/Cargo.toml +++ b/utils/wasm-builder/Cargo.toml @@ -19,3 +19,6 @@ fs2 = "0.4.3" wasm-gc-api = "0.1.11" atty = "0.2.13" itertools = "0.8.2" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"]